", network, -1)
// Generate hosts for a primary network domain
primaryNetworkDomainGen := rapidgen.DomainWithSuffixAndPort(primaryDomainSuffix, nil)
@@ -4553,7 +4424,7 @@ func TestSendMessageCallbacks(t *testing.T) {
netA, netB, _, closeFunc := setupWebsocketNetworkAB(t, 2)
defer closeFunc()
- var counter uint64
+ var counter atomic.Uint64
require.NotZero(t, netA.NumPeers())
// peerB is netA's representation of netB and vice versa
@@ -4567,10 +4438,10 @@ func TestSendMessageCallbacks(t *testing.T) {
// and goes through the actual response code path to generate and send TS responses to netB
for i := 0; i < 100; i++ {
randInt := crypto.RandUint64()%(128) + 1
- atomic.AddUint64(&counter, randInt)
+ counter.Add(randInt)
topic := MakeTopic("val", []byte("blah"))
callback := func() {
- atomic.AddUint64(&counter, ^uint64(randInt-1))
+ counter.Add(^uint64(randInt - 1))
}
msg := IncomingMessage{Sender: peerB, Tag: protocol.UniEnsBlockReqTag}
peerB.Respond(context.Background(), msg, OutgoingMessage{OnRelease: callback, Topics: Topics{topic}})
@@ -4579,14 +4450,14 @@ func TestSendMessageCallbacks(t *testing.T) {
// of outstanding TS requests below 0. This will be true because we never made any UE block requests, we only
// simulated them by manually creating a IncomingMessage with the UE tag in the loop above
require.Eventually(t,
- func() bool { return atomic.LoadInt64(&peerA.outstandingTopicRequests) < 0 },
+ func() bool { return peerA.outstandingTopicRequests.Load() < 0 },
500*time.Millisecond,
25*time.Millisecond,
)
// confirm that the test counter decrements down to zero correctly through callbacks
require.Eventually(t,
- func() bool { return atomic.LoadUint64(&counter) == uint64(0) },
+ func() bool { return counter.Load() == uint64(0) },
500*time.Millisecond,
25*time.Millisecond,
)
diff --git a/network/wsPeer.go b/network/wsPeer.go
index a717eef60c..55dba8e568 100644
--- a/network/wsPeer.go
+++ b/network/wsPeer.go
@@ -120,6 +120,7 @@ var defaultSendMessageTags = map[protocol.Tag]bool{
// interface allows substituting debug implementation for *websocket.Conn
type wsPeerWebsocketConn interface {
+ RemoteAddr() net.Addr
RemoteAddrString() string
NextReader() (int, io.Reader, error)
WriteMessage(int, []byte) error
@@ -202,27 +203,24 @@ type wsPeer struct {
// lastPacketTime contains the UnixNano at the last time a successful communication was made with the peer.
// "successful communication" above refers to either reading from or writing to a connection without receiving any
// error.
- // we want this to be a 64-bit aligned for atomics support on 32bit platforms.
- lastPacketTime int64
+ lastPacketTime atomic.Int64
// outstandingTopicRequests is an atomic counter for the number of outstanding block requests we've made out to this peer
// if a peer sends more blocks than we've requested, we'll disconnect from it.
- outstandingTopicRequests int64
+ outstandingTopicRequests atomic.Int64
// intermittentOutgoingMessageEnqueueTime contains the UnixNano of the message's enqueue time that is currently being written to the
// peer, or zero if no message is being written.
- intermittentOutgoingMessageEnqueueTime int64
+ intermittentOutgoingMessageEnqueueTime atomic.Int64
// Nonce used to uniquely identify requests
- requestNonce uint64
+ requestNonce atomic.Uint64
// duplicateFilterCount counts how many times the remote peer has sent us a message hash
// to filter that it had already sent before.
- // this needs to be 64-bit aligned for use with atomic.AddUint64 on 32-bit platforms.
- duplicateFilterCount uint64
+ duplicateFilterCount atomic.Uint64
- // These message counters need to be 64-bit aligned as well.
- txMessageCount, miMessageCount, ppMessageCount, avMessageCount, unkMessageCount uint64
+ txMessageCount, miMessageCount, ppMessageCount, avMessageCount, unkMessageCount atomic.Uint64
wsPeerCore
@@ -239,8 +237,8 @@ type wsPeer struct {
wg sync.WaitGroup
- didSignalClose int32
- didInnerClose int32
+ didSignalClose atomic.Int32
+ didInnerClose atomic.Int32
TelemetryGUID string
InstanceName string
@@ -262,7 +260,7 @@ type wsPeer struct {
// the peer's identity key which it uses for identityChallenge exchanges
identity crypto.PublicKey
- identityVerified uint32
+ identityVerified atomic.Uint32
// the identityChallenge is recorded to the peer so it may verify its identity at a later time
identityChallenge identityChallengeValue
@@ -292,7 +290,7 @@ type wsPeer struct {
sendMessageTag map[protocol.Tag]bool
// messagesOfInterestGeneration is this node's messagesOfInterest version that we have seen to this peer.
- messagesOfInterestGeneration uint32
+ messagesOfInterestGeneration atomic.Uint32
// connMonitor used to measure the relative performance of the connection
// compared to the other outgoing connections. Incoming connections would have this
@@ -324,6 +322,12 @@ type HTTPPeer interface {
GetHTTPClient() *http.Client
}
+// IPAddressable is addressable with either IPv4 or IPv6 address
+type IPAddressable interface {
+ IPAddr() []byte
+ RoutingAddr() []byte
+}
+
// UnicastPeer is another possible interface for the opaque Peer.
// It is possible that we can only initiate a connection to a peer over websockets.
type UnicastPeer interface {
@@ -372,6 +376,45 @@ func (wp *wsPeer) Version() string {
return wp.version
}
+func (wp *wsPeer) IPAddr() []byte {
+ remote := wp.conn.RemoteAddr()
+ if remote == nil {
+ return nil
+ }
+ ip := remote.(*net.TCPAddr).IP
+ result := ip.To4()
+ if result == nil {
+ result = ip.To16()
+ }
+ return result
+}
+
+// RoutingAddr returns meaningful routing part of the address:
+// ipv4 for ipv4 addresses
+// top 8 bytes of ipv6 for ipv6 addresses
+// low 4 bytes for ipv4 embedded into ipv6
+// see http://www.tcpipguide.com/free/t_IPv6IPv4AddressEmbedding.htm for details.
+func (wp *wsPeer) RoutingAddr() []byte {
+ isZeros := func(ip []byte) bool {
+ for i := 0; i < len(ip); i++ {
+ if ip[i] != 0 {
+ return false
+ }
+ }
+ return true
+ }
+
+ ip := wp.IPAddr()
+ if len(ip) != net.IPv6len {
+ return ip
+ }
+ // ipv6, check if it's ipv4 embedded
+ if isZeros(ip[0:10]) {
+ return ip[12:16]
+ }
+ return ip[0:8]
+}
+
// Unicast sends the given bytes to this specific peer. Does not wait for message to be sent.
// (Implements UnicastPeer)
func (wp *wsPeer) Unicast(ctx context.Context, msg []byte, tag protocol.Tag) error {
@@ -457,7 +500,7 @@ func (wp *wsPeer) init(config config.Local, sendBufferLength int) {
wp.closing = make(chan struct{})
wp.sendBufferHighPrio = make(chan sendMessages, sendBufferLength)
wp.sendBufferBulk = make(chan sendMessages, sendBufferLength)
- atomic.StoreInt64(&wp.lastPacketTime, time.Now().UnixNano())
+ wp.lastPacketTime.Store(time.Now().UnixNano())
wp.responseChannels = make(map[uint64]chan *Response)
wp.sendMessageTag = defaultSendMessageTags
wp.clientDataStore = make(map[string]interface{})
@@ -487,7 +530,7 @@ func (wp *wsPeer) OriginAddress() string {
func (wp *wsPeer) reportReadErr(err error) {
// only report error if we haven't already closed the peer
- if atomic.LoadInt32(&wp.didInnerClose) == 0 {
+ if wp.didInnerClose.Load() == 0 {
_, _, line, _ := runtime.Caller(1)
wp.log.Warnf("peer[%s] line=%d read err: %s", wp.conn.RemoteAddrString(), line, err)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "reader err"})
@@ -541,10 +584,10 @@ func (wp *wsPeer) readLoop() {
// Skip the message if it's a response to a request we didn't make or has timed out
if msg.Tag == protocol.TopicMsgRespTag && wp.lenResponseChannels() == 0 {
- atomic.AddInt64(&wp.outstandingTopicRequests, -1)
+ wp.outstandingTopicRequests.Add(-1)
// This peers has sent us more responses than we have requested. This is a protocol violation and we should disconnect.
- if atomic.LoadInt64(&wp.outstandingTopicRequests) < 0 {
+ if wp.outstandingTopicRequests.Load() < 0 {
wp.log.Errorf("wsPeer readloop: peer %s sent TS response without a request", wp.conn.RemoteAddrString())
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "unrequestedTS"})
cleanupCloseError = disconnectUnexpectedTopicResp
@@ -578,7 +621,7 @@ func (wp *wsPeer) readLoop() {
return
}
msg.Net = wp.net
- atomic.StoreInt64(&wp.lastPacketTime, msg.Received)
+ wp.lastPacketTime.Store(msg.Received)
networkReceivedBytesTotal.AddUint64(uint64(len(msg.Data)+2), nil)
networkMessageReceivedTotal.AddUint64(1, nil)
networkReceivedBytesByTag.Add(string(tag[:]), uint64(len(msg.Data)+2))
@@ -594,7 +637,7 @@ func (wp *wsPeer) readLoop() {
switch msg.Tag {
case protocol.MsgOfInterestTag:
// try to decode the message-of-interest
- atomic.AddUint64(&wp.miMessageCount, 1)
+ wp.miMessageCount.Add(1)
if close, reason := wp.handleMessageOfInterest(msg); close {
cleanupCloseError = reason
if reason == disconnectBadData {
@@ -604,7 +647,7 @@ func (wp *wsPeer) readLoop() {
}
continue
case protocol.TopicMsgRespTag: // Handle Topic message
- atomic.AddInt64(&wp.outstandingTopicRequests, -1)
+ wp.outstandingTopicRequests.Add(-1)
topics, err := UnmarshallTopics(msg.Data)
if err != nil {
wp.log.Warnf("wsPeer readLoop: could not read the message from: %s %s", wp.conn.RemoteAddrString(), err)
@@ -634,17 +677,17 @@ func (wp *wsPeer) readLoop() {
wp.handleFilterMessage(msg)
continue
case protocol.TxnTag:
- atomic.AddUint64(&wp.txMessageCount, 1)
+ wp.txMessageCount.Add(1)
case protocol.AgreementVoteTag:
- atomic.AddUint64(&wp.avMessageCount, 1)
+ wp.avMessageCount.Add(1)
case protocol.ProposalPayloadTag:
- atomic.AddUint64(&wp.ppMessageCount, 1)
+ wp.ppMessageCount.Add(1)
// the remaining valid tags: no special handling here
case protocol.NetPrioResponseTag, protocol.PingTag, protocol.PingReplyTag,
protocol.StateProofSigTag, protocol.UniEnsBlockReqTag, protocol.VoteBundleTag, protocol.NetIDVerificationTag:
default: // unrecognized tag
unknownProtocolTagMessagesTotal.Inc(nil)
- atomic.AddUint64(&wp.unkMessageCount, 1)
+ wp.unkMessageCount.Add(1)
continue // drop message, skip adding it to queue
// TODO: should disconnect here?
}
@@ -740,7 +783,7 @@ func (wp *wsPeer) handleFilterMessage(msg IncomingMessage) {
// large message concurrently from several peers, and then sent the filter message to us after
// each large message finished transferring.
duplicateNetworkFilterReceivedTotal.Inc(nil)
- atomic.AddUint64(&wp.duplicateFilterCount, 1)
+ wp.duplicateFilterCount.Add(1)
}
}
@@ -792,17 +835,17 @@ func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason {
return disconnectStaleWrite
}
- atomic.StoreInt64(&wp.intermittentOutgoingMessageEnqueueTime, msg.enqueued.UnixNano())
- defer atomic.StoreInt64(&wp.intermittentOutgoingMessageEnqueueTime, 0)
+ wp.intermittentOutgoingMessageEnqueueTime.Store(msg.enqueued.UnixNano())
+ defer wp.intermittentOutgoingMessageEnqueueTime.Store(0)
err := wp.conn.WriteMessage(websocket.BinaryMessage, msg.data)
if err != nil {
- if atomic.LoadInt32(&wp.didInnerClose) == 0 {
+ if wp.didInnerClose.Load() == 0 {
wp.log.Warn("peer write error ", err)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "write err"})
}
return disconnectWriteError
}
- atomic.StoreInt64(&wp.lastPacketTime, time.Now().UnixNano())
+ wp.lastPacketTime.Store(time.Now().UnixNano())
networkSentBytesTotal.AddUint64(uint64(len(msg.data)), nil)
networkSentBytesByTag.Add(string(tag), uint64(len(msg.data)))
networkMessageSentTotal.AddUint64(1, nil)
@@ -936,7 +979,7 @@ func (wp *wsPeer) pingTimes() (lastPingSent time.Time, lastPingRoundTripTime tim
// called when the connection had an error or closed remotely
func (wp *wsPeer) internalClose(reason disconnectReason) {
- if atomic.CompareAndSwapInt32(&wp.didSignalClose, 0, 1) {
+ if wp.didSignalClose.CompareAndSwap(0, 1) {
wp.net.peerRemoteClose(wp, reason)
}
wp.Close(time.Now().Add(peerDisconnectionAckDuration))
@@ -944,16 +987,16 @@ func (wp *wsPeer) internalClose(reason disconnectReason) {
// called either here or from above enclosing node logic
func (wp *wsPeer) Close(deadline time.Time) {
- atomic.StoreInt32(&wp.didSignalClose, 1)
- if atomic.CompareAndSwapInt32(&wp.didInnerClose, 0, 1) {
+ wp.didSignalClose.Store(1)
+ if wp.didInnerClose.CompareAndSwap(0, 1) {
close(wp.closing)
err := wp.conn.CloseWithMessage(websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), deadline)
if err != nil {
- wp.log.Infof("failed to write CloseMessage to connection for %s", wp.conn.RemoteAddrString())
+ wp.log.Infof("failed to write CloseMessage to connection for %s, err: %s", wp.conn.RemoteAddrString(), err)
}
err = wp.conn.CloseWithoutFlush()
if err != nil {
- wp.log.Infof("failed to CloseWithoutFlush to connection for %s", wp.conn.RemoteAddrString())
+ wp.log.Infof("failed to CloseWithoutFlush to connection for %s, err: %s", wp.conn.RemoteAddrString(), err)
}
}
@@ -984,11 +1027,11 @@ func (wp *wsPeer) CloseAndWait(deadline time.Time) {
}
func (wp *wsPeer) GetLastPacketTime() int64 {
- return atomic.LoadInt64(&wp.lastPacketTime)
+ return wp.lastPacketTime.Load()
}
func (wp *wsPeer) CheckSlowWritingPeer(now time.Time) bool {
- ongoingMessageTime := atomic.LoadInt64(&wp.intermittentOutgoingMessageEnqueueTime)
+ ongoingMessageTime := wp.intermittentOutgoingMessageEnqueueTime.Load()
if ongoingMessageTime == 0 {
return false
}
@@ -1000,7 +1043,7 @@ func (wp *wsPeer) CheckSlowWritingPeer(now time.Time) bool {
// The value is stored on wsPeer
func (wp *wsPeer) getRequestNonce() []byte {
buf := make([]byte, binary.MaxVarintLen64)
- binary.PutUvarint(buf, atomic.AddUint64(&wp.requestNonce, 1))
+ binary.PutUvarint(buf, wp.requestNonce.Add(1))
return buf
}
@@ -1016,7 +1059,7 @@ func MakeNonceTopic(nonce uint64) Topic {
func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Response, e error) {
// Add nonce, stored on the wsPeer as the topic
- nonceTopic := MakeNonceTopic(atomic.AddUint64(&wp.requestNonce, 1))
+ nonceTopic := MakeNonceTopic(wp.requestNonce.Add(1))
topics = append(topics, nonceTopic)
// serialize the topics
@@ -1038,7 +1081,7 @@ func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Re
ctx: context.Background()}
select {
case wp.sendBufferBulk <- sendMessages{msgs: msg}:
- atomic.AddInt64(&wp.outstandingTopicRequests, 1)
+ wp.outstandingTopicRequests.Add(1)
case <-wp.closing:
e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddrString())
return
@@ -1102,7 +1145,7 @@ func (wp *wsPeer) sendMessagesOfInterest(messagesOfInterestGeneration uint32, me
if err != nil {
wp.log.Errorf("ws send msgOfInterest: %v", err)
} else {
- atomic.StoreUint32(&wp.messagesOfInterestGeneration, messagesOfInterestGeneration)
+ wp.messagesOfInterestGeneration.Store(messagesOfInterestGeneration)
}
}
diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go
index 4853b95e32..59217047ce 100644
--- a/network/wsPeer_test.go
+++ b/network/wsPeer_test.go
@@ -22,12 +22,14 @@ import (
"go/ast"
"go/parser"
"go/token"
+ "io"
+ "net"
"path/filepath"
"sort"
"strings"
+ "sync/atomic"
"testing"
"time"
- "unsafe"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
@@ -41,17 +43,17 @@ func TestCheckSlowWritingPeer(t *testing.T) {
now := time.Now()
peer := wsPeer{
- intermittentOutgoingMessageEnqueueTime: 0,
+ intermittentOutgoingMessageEnqueueTime: atomic.Int64{},
wsPeerCore: wsPeerCore{net: &WebsocketNetwork{
log: logging.TestingLog(t),
}},
}
require.Equal(t, peer.CheckSlowWritingPeer(now), false)
- peer.intermittentOutgoingMessageEnqueueTime = now.UnixNano()
+ peer.intermittentOutgoingMessageEnqueueTime.Store(now.UnixNano())
require.Equal(t, peer.CheckSlowWritingPeer(now), false)
- peer.intermittentOutgoingMessageEnqueueTime = now.Add(-maxMessageQueueDuration * 2).UnixNano()
+ peer.intermittentOutgoingMessageEnqueueTime.Store(now.Add(-maxMessageQueueDuration * 2).UnixNano())
require.Equal(t, peer.CheckSlowWritingPeer(now), true)
}
@@ -99,24 +101,6 @@ func TestDefaultMessageTagsLength(t *testing.T) {
}
}
-// TestAtomicVariablesAlignment ensures that the 64-bit atomic variables
-// offsets are 64-bit aligned. This is required due to go atomic library
-// limitation.
-func TestAtomicVariablesAlignment(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- p := wsPeer{}
- require.True(t, (unsafe.Offsetof(p.requestNonce)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.lastPacketTime)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.intermittentOutgoingMessageEnqueueTime)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.duplicateFilterCount)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.txMessageCount)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.miMessageCount)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.ppMessageCount)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.avMessageCount)%8) == 0)
- require.True(t, (unsafe.Offsetof(p.unkMessageCount)%8) == 0)
-}
-
func TestTagCounterFiltering(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -282,3 +266,48 @@ func getProtocolTags(t *testing.T) []string {
require.Len(t, declaredTags, len(protocol.TagList))
return declaredTags
}
+
+type tcpipMockConn struct{ addr net.TCPAddr }
+
+func (m *tcpipMockConn) RemoteAddr() net.Addr { return &m.addr }
+func (m *tcpipMockConn) RemoteAddrString() string { return "" }
+func (m *tcpipMockConn) NextReader() (int, io.Reader, error) { return 0, nil, nil }
+func (m *tcpipMockConn) WriteMessage(int, []byte) error { return nil }
+func (m *tcpipMockConn) CloseWithMessage([]byte, time.Time) error { return nil }
+func (m *tcpipMockConn) SetReadLimit(int64) {}
+func (m *tcpipMockConn) CloseWithoutFlush() error { return nil }
+func (m *tcpipMockConn) UnderlyingConn() net.Conn { return nil }
+
+func TestWsPeerIPAddr(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ conn := &tcpipMockConn{}
+ peer := wsPeer{
+ conn: conn,
+ }
+ // some raw IPv4 address
+ conn.addr.IP = []byte{127, 0, 0, 1}
+ require.Equal(t, []byte{127, 0, 0, 1}, peer.IPAddr())
+ require.Equal(t, []byte{127, 0, 0, 1}, peer.RoutingAddr())
+
+ // IPv4 constructed from net.IPv4
+ conn.addr.IP = net.IPv4(127, 0, 0, 2)
+ require.Equal(t, []byte{127, 0, 0, 2}, peer.IPAddr())
+ require.Equal(t, []byte{127, 0, 0, 2}, peer.RoutingAddr())
+
+ // some IPv6 address
+ conn.addr.IP = net.IPv6linklocalallrouters
+ require.Equal(t, []byte(net.IPv6linklocalallrouters), peer.IPAddr())
+ require.Equal(t, []byte(net.IPv6linklocalallrouters[0:8]), peer.RoutingAddr())
+
+ // embedded IPv4 into IPv6
+ conn.addr.IP = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 3}
+ require.Equal(t, 16, len(conn.addr.IP))
+ require.Equal(t, []byte{127, 0, 0, 3}, peer.IPAddr())
+ require.Equal(t, []byte{127, 0, 0, 3}, peer.RoutingAddr())
+ conn.addr.IP = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 4}
+ require.Equal(t, 16, len(conn.addr.IP))
+ require.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 4}, peer.IPAddr())
+ require.Equal(t, []byte{127, 0, 0, 4}, peer.RoutingAddr())
+}
diff --git a/node/follower_node.go b/node/follower_node.go
index e044333d42..66790b1291 100644
--- a/node/follower_node.go
+++ b/node/follower_node.go
@@ -20,8 +20,6 @@ package node
import (
"context"
"fmt"
- "os"
- "path/filepath"
"time"
"github.com/algorand/go-deadlock"
@@ -59,7 +57,7 @@ type AlgorandFollowerNode struct {
catchpointCatchupService *catchup.CatchpointCatchupService
blockService *rpcs.BlockService
- rootDir string
+ genesisDirs config.ResolvedGenesisDirs
genesisID string
genesisHash crypto.Digest
devMode bool // is this node operates in a developer mode ? ( benign agreement, broadcasting transaction generates a new block )
@@ -80,11 +78,15 @@ type AlgorandFollowerNode struct {
// MakeFollower sets up an Algorand data node
func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phonebookAddresses []string, genesis bookkeeping.Genesis) (*AlgorandFollowerNode, error) {
node := new(AlgorandFollowerNode)
- node.rootDir = rootDir
node.log = log.With("name", cfg.NetAddress)
node.genesisID = genesis.ID()
node.genesisHash = genesis.Hash()
node.devMode = genesis.DevMode
+ var err error
+ node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID())
+ if err != nil {
+ return nil, err
+ }
if node.devMode {
log.Warn("Follower running on a devMode network. Must submit txns to a different node.")
@@ -102,16 +104,6 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo
p2pNode.DeregisterMessageInterest(protocol.VoteBundleTag)
node.net = p2pNode
- // load stored data
- genesisDir := filepath.Join(rootDir, genesis.ID())
- ledgerPathnamePrefix := filepath.Join(genesisDir, config.LedgerFilenamePrefix)
-
- // create initial ledger, if it doesn't exist
- err = os.Mkdir(genesisDir, 0700)
- if err != nil && !os.IsExist(err) {
- log.Errorf("Unable to create genesis directory: %v", err)
- return nil, err
- }
genalloc, err := genesis.Balances()
if err != nil {
log.Errorf("Cannot load genesis allocation: %v", err)
@@ -120,9 +112,13 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo
node.cryptoPool = execpool.MakePool(node)
node.lowPriorityCryptoVerificationPool = execpool.MakeBacklog(node.cryptoPool, 2*node.cryptoPool.GetParallelism(), execpool.LowPriority, node)
- node.ledger, err = data.LoadLedger(node.log, ledgerPathnamePrefix, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, []ledgercore.BlockListener{}, cfg)
+ ledgerPaths := ledger.DirsAndPrefix{
+ DBFilePrefix: config.LedgerFilenamePrefix,
+ ResolvedGenesisDirs: node.genesisDirs,
+ }
+ node.ledger, err = data.LoadLedger(node.log, ledgerPaths, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, []ledgercore.BlockListener{}, cfg)
if err != nil {
- log.Errorf("Cannot initialize ledger (%s): %v", ledgerPathnamePrefix, err)
+ log.Errorf("Cannot initialize ledger (%v): %v", ledgerPaths, err)
return nil, err
}
@@ -230,24 +226,24 @@ func (node *AlgorandFollowerNode) Ledger() *data.Ledger {
// BroadcastSignedTxGroup errors in follower mode
func (node *AlgorandFollowerNode) BroadcastSignedTxGroup(_ []transactions.SignedTxn) (err error) {
- return fmt.Errorf("cannot broadcast txns in sync mode")
+ return fmt.Errorf("cannot broadcast txns in follower mode")
}
// AsyncBroadcastSignedTxGroup errors in follower mode
func (node *AlgorandFollowerNode) AsyncBroadcastSignedTxGroup(_ []transactions.SignedTxn) (err error) {
- return fmt.Errorf("cannot broadcast txns in sync mode")
+ return fmt.Errorf("cannot broadcast txns in follower mode")
}
// BroadcastInternalSignedTxGroup errors in follower mode
func (node *AlgorandFollowerNode) BroadcastInternalSignedTxGroup(_ []transactions.SignedTxn) (err error) {
- return fmt.Errorf("cannot broadcast internal signed txn group in sync mode")
+ return fmt.Errorf("cannot broadcast internal signed txn group in follower mode")
}
// Simulate speculatively runs a transaction group against the current
// blockchain state and returns the effects and/or errors that would result.
-func (node *AlgorandFollowerNode) Simulate(_ simulation.Request) (result simulation.Result, err error) {
- err = fmt.Errorf("cannot simulate in data mode")
- return
+func (node *AlgorandFollowerNode) Simulate(request simulation.Request) (result simulation.Result, err error) {
+ simulator := simulation.MakeSimulator(node.ledger, node.config.EnableDeveloperAPI)
+ return simulator.Simulate(request)
}
// GetPendingTransaction no-ops in follower mode
diff --git a/node/follower_node_test.go b/node/follower_node_test.go
index 192b333be0..b6402a9b57 100644
--- a/node/follower_node_test.go
+++ b/node/follower_node_test.go
@@ -18,6 +18,7 @@ package node
import (
"context"
+ "path/filepath"
"testing"
"github.com/sirupsen/logrus"
@@ -31,6 +32,7 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/txntest"
"github.com/algorand/go-algorand/ledger/simulation"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
@@ -65,7 +67,8 @@ func setupFollowNode(t *testing.T) *AlgorandFollowerNode {
cfg := config.GetDefaultLocal()
cfg.EnableFollowMode = true
genesis := followNodeDefaultGenesis()
- node, err := MakeFollower(logging.Base(), t.TempDir(), cfg, []string{}, genesis)
+ root := t.TempDir()
+ node, err := MakeFollower(logging.Base(), root, cfg, []string{}, genesis)
require.NoError(t, err)
return node
}
@@ -136,7 +139,8 @@ func TestDevModeWarning(t *testing.T) {
logger, hook := test.NewNullLogger()
tlogger := logging.NewWrappedLogger(logger)
- _, err := MakeFollower(tlogger, t.TempDir(), cfg, []string{}, genesis)
+ root := t.TempDir()
+ _, err := MakeFollower(tlogger, root, cfg, []string{}, genesis)
require.NoError(t, err)
// check for the warning
@@ -169,3 +173,169 @@ func TestFastCatchupResume(t *testing.T) {
// Verify the sync was reset.
assert.Equal(t, uint64(0), node.GetSyncRound())
}
+
+// TestDefaultResourcePaths confirms that when no extra configuration is provided, all resources are created in the dataDir
+func TestDefaultResourcePaths_Follower(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ testDirectory := t.TempDir()
+
+ genesis := bookkeeping.Genesis{
+ SchemaID: "go-test-node-genesis",
+ Proto: protocol.ConsensusCurrentVersion,
+ Network: config.Devtestnet,
+ FeeSink: sinkAddr.String(),
+ RewardsPool: poolAddr.String(),
+ }
+
+ cfg := config.GetDefaultLocal()
+
+ // the logger is set up by the server, so we don't test this here
+ log := logging.Base()
+
+ n, err := MakeFollower(log, testDirectory, cfg, []string{}, genesis)
+ require.NoError(t, err)
+
+ n.Start()
+ defer n.Stop()
+
+ // confirm genesis dir exists in the data dir, and that resources exist in the expected locations
+ require.DirExists(t, filepath.Join(testDirectory, genesis.ID()))
+
+ require.FileExists(t, filepath.Join(testDirectory, genesis.ID(), "ledger.tracker.sqlite"))
+ require.FileExists(t, filepath.Join(testDirectory, genesis.ID(), "ledger.block.sqlite"))
+}
+
+// TestConfiguredDataDirs tests to see that when HotDataDir and ColdDataDir are set, underlying resources are created in the correct locations
+// Not all resources are tested here, because not all resources use the paths provided to them immediately. For example, catchpoint only creates
+// a directory when writing a catchpoint file, which is not being done here with this simple node
+func TestConfiguredDataDirs_Follower(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ testDirectory := t.TempDir()
+ testDirHot := t.TempDir()
+ testDirCold := t.TempDir()
+
+ genesis := bookkeeping.Genesis{
+ SchemaID: "go-test-node-genesis",
+ Proto: protocol.ConsensusCurrentVersion,
+ Network: config.Devtestnet,
+ FeeSink: sinkAddr.String(),
+ RewardsPool: poolAddr.String(),
+ }
+
+ cfg := config.GetDefaultLocal()
+
+ cfg.HotDataDir = testDirHot
+ cfg.ColdDataDir = testDirCold
+ cfg.CatchpointTracking = 2
+ cfg.CatchpointInterval = 1
+
+ // the logger is set up by the server, so we don't test this here
+ log := logging.Base()
+
+ n, err := MakeFollower(log, testDirectory, cfg, []string{}, genesis)
+ require.NoError(t, err)
+
+ n.Start()
+ defer n.Stop()
+
+ // confirm hot data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirHot, genesis.ID()))
+
+ // confirm the tracker is in the genesis dir of hot data dir
+ require.FileExists(t, filepath.Join(testDirHot, genesis.ID(), "ledger.tracker.sqlite"))
+
+ // confirm cold data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirCold, genesis.ID()))
+
+ // confirm the blockdb is in the genesis dir of cold data dir
+ require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "ledger.block.sqlite"))
+
+}
+
+// TestConfiguredResourcePaths tests to see that when individual paths are set, underlying resources are created in the correct locations
+func TestConfiguredResourcePaths_Follower(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ testDirectory := t.TempDir()
+ testDirHot := t.TempDir()
+ testDirCold := t.TempDir()
+
+ // add a path for each resource now
+ trackerPath := filepath.Join(testDirectory, "custom_tracker")
+ blockPath := filepath.Join(testDirectory, "custom_block")
+
+ genesis := bookkeeping.Genesis{
+ SchemaID: "go-test-node-genesis",
+ Proto: protocol.ConsensusCurrentVersion,
+ Network: config.Devtestnet,
+ FeeSink: sinkAddr.String(),
+ RewardsPool: poolAddr.String(),
+ }
+
+ cfg := config.GetDefaultLocal()
+
+ // Configure everything even though a follower node will only use Tracker and Block DBs
+ cfg.HotDataDir = testDirHot
+ cfg.ColdDataDir = testDirCold
+ cfg.TrackerDBDir = trackerPath
+ cfg.BlockDBDir = blockPath
+ cfg.CatchpointTracking = 2
+ cfg.CatchpointInterval = 1
+
+ // the logger is set up by the server, so we don't test this here
+ log := logging.Base()
+
+ n, err := MakeFollower(log, testDirectory, cfg, []string{}, genesis)
+ require.NoError(t, err)
+
+ n.Start()
+ defer n.Stop()
+
+ // confirm hot data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirHot, genesis.ID()))
+
+ // the tracker shouldn't be in the hot data dir, but rather the custom path's genesis dir
+ require.NoFileExists(t, filepath.Join(testDirHot, genesis.ID(), "ledger.tracker.sqlite"))
+ require.FileExists(t, filepath.Join(cfg.TrackerDBDir, genesis.ID(), "ledger.tracker.sqlite"))
+
+ // confirm cold data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirCold, genesis.ID()))
+
+ // block db shouldn't be in the cold data dir, but rather the custom path's genesis dir
+ require.NoFileExists(t, filepath.Join(testDirCold, genesis.ID(), "ledger.block.sqlite"))
+ require.FileExists(t, filepath.Join(cfg.BlockDBDir, genesis.ID(), "ledger.block.sqlite"))
+}
+
+func TestSimulate(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+ node := setupFollowNode(t)
+
+ round := node.ledger.LastRound()
+
+ stxn := txntest.Txn{
+ Type: protocol.PaymentTx,
+ Sender: sinkAddr,
+ Receiver: poolAddr,
+ Amount: 1,
+ Fee: 1000,
+ FirstValid: round,
+ LastValid: round + 1000,
+ GenesisHash: node.ledger.GenesisHash(),
+ }.SignedTxn()
+
+ request := simulation.Request{
+ TxnGroups: [][]transactions.SignedTxn{{stxn}},
+ AllowEmptySignatures: true,
+ }
+
+ result, err := node.Simulate(request)
+ require.NoError(t, err)
+
+ require.Len(t, result.TxnGroups, 1)
+ require.Len(t, result.TxnGroups[0].Txns, 1)
+ require.Equal(t, stxn, result.TxnGroups[0].Txns[0].Txn.SignedTxn)
+ require.Empty(t, result.TxnGroups[0].FailureMessage)
+}
diff --git a/node/msgp_gen.go b/node/msgp_gen.go
index 146e8635bb..9d79065fe8 100644
--- a/node/msgp_gen.go
+++ b/node/msgp_gen.go
@@ -14,6 +14,7 @@ import (
// |-----> (*) MarshalMsg
// |-----> (*) CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> (*) Msgsize
// |-----> (*) MsgIsZero
@@ -23,6 +24,7 @@ import (
// |-----> (*) MarshalMsg
// |-----> (*) CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> (*) Msgsize
// |-----> (*) MsgIsZero
@@ -57,7 +59,12 @@ func (_ *netPrioResponse) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *netPrioResponse) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *netPrioResponse) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
var field []byte
_ = field
var zb0001 int
@@ -139,6 +146,9 @@ func (z *netPrioResponse) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *netPrioResponse) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *netPrioResponse) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*netPrioResponse)
return ok
@@ -229,7 +239,12 @@ func (_ *netPrioResponseSigned) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *netPrioResponseSigned) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
var field []byte
_ = field
var zb0001 int
@@ -321,7 +336,7 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).Round.UnmarshalMsg(bts)
+ bts, err = (*z).Round.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Round")
return
@@ -329,7 +344,7 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).Sender.UnmarshalMsg(bts)
+ bts, err = (*z).Sender.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Sender")
return
@@ -337,7 +352,7 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).Sig.UnmarshalMsg(bts)
+ bts, err = (*z).Sig.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Sig")
return
@@ -443,19 +458,19 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
}
case "Round":
- bts, err = (*z).Round.UnmarshalMsg(bts)
+ bts, err = (*z).Round.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Round")
return
}
case "Sender":
- bts, err = (*z).Sender.UnmarshalMsg(bts)
+ bts, err = (*z).Sender.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Sender")
return
}
case "Sig":
- bts, err = (*z).Sig.UnmarshalMsg(bts)
+ bts, err = (*z).Sig.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Sig")
return
@@ -473,6 +488,9 @@ func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *netPrioResponseSigned) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *netPrioResponseSigned) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*netPrioResponseSigned)
return ok
diff --git a/node/node.go b/node/node.go
index 477c1b794c..4c18ad1d51 100644
--- a/node/node.go
+++ b/node/node.go
@@ -127,7 +127,7 @@ type AlgorandFullNode struct {
ledgerService *rpcs.LedgerService
txPoolSyncerService *rpcs.TxSyncer
- rootDir string
+ genesisDirs config.ResolvedGenesisDirs
genesisID string
genesisHash crypto.Digest
devMode bool // is this node operating in a developer mode ? ( benign agreement, broadcasting transaction generates a new block )
@@ -177,44 +177,54 @@ type TxnWithStatus struct {
// (i.e., it returns a node that participates in consensus)
func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAddresses []string, genesis bookkeeping.Genesis) (*AlgorandFullNode, error) {
node := new(AlgorandFullNode)
- node.rootDir = rootDir
node.log = log.With("name", cfg.NetAddress)
node.genesisID = genesis.ID()
node.genesisHash = genesis.Hash()
node.devMode = genesis.DevMode
node.config = cfg
-
- // tie network, block fetcher, and agreement services together
- p2pNode, err := network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, node)
+ var err error
+ node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID())
if err != nil {
- log.Errorf("could not create websocket node: %v", err)
return nil, err
}
- p2pNode.SetPrioScheme(node)
- node.net = p2pNode
-
- // load stored data
- genesisDir := filepath.Join(rootDir, genesis.ID())
- ledgerPathnamePrefix := filepath.Join(genesisDir, config.LedgerFilenamePrefix)
- // create initial ledger, if it doesn't exist
- err = os.Mkdir(genesisDir, 0700)
- if err != nil && !os.IsExist(err) {
- log.Errorf("Unable to create genesis directory: %v", err)
- return nil, err
- }
genalloc, err := genesis.Balances()
if err != nil {
log.Errorf("Cannot load genesis allocation: %v", err)
return nil, err
}
+ // tie network, block fetcher, and agreement services together
+ var p2pNode network.GossipNode
+ if cfg.EnableP2P {
+ // TODO: pass more appropriate genesisDir (hot/cold). Presently this is just used to store a peerID key.
+ p2pNode, err = network.NewP2PNetwork(node.log, node.config, node.genesisDirs.RootGenesisDir, phonebookAddresses, genesis.ID(), genesis.Network)
+ if err != nil {
+ log.Errorf("could not create p2p node: %v", err)
+ return nil, err
+ }
+ } else {
+ var wsNode *network.WebsocketNetwork
+ wsNode, err = network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, node)
+ if err != nil {
+ log.Errorf("could not create websocket node: %v", err)
+ return nil, err
+ }
+ wsNode.SetPrioScheme(node)
+ p2pNode = wsNode
+ }
+ node.net = p2pNode
+
node.cryptoPool = execpool.MakePool(node)
node.lowPriorityCryptoVerificationPool = execpool.MakeBacklog(node.cryptoPool, 2*node.cryptoPool.GetParallelism(), execpool.LowPriority, node)
node.highPriorityCryptoVerificationPool = execpool.MakeBacklog(node.cryptoPool, 2*node.cryptoPool.GetParallelism(), execpool.HighPriority, node)
- node.ledger, err = data.LoadLedger(node.log, ledgerPathnamePrefix, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, []ledgercore.BlockListener{}, cfg)
+ ledgerPaths := ledger.DirsAndPrefix{
+ DBFilePrefix: config.LedgerFilenamePrefix,
+ ResolvedGenesisDirs: node.genesisDirs,
+ }
+ node.ledger, err = data.LoadLedger(node.log, ledgerPaths, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, []ledgercore.BlockListener{}, cfg)
if err != nil {
- log.Errorf("Cannot initialize ledger (%s): %v", ledgerPathnamePrefix, err)
+ log.Errorf("Cannot initialize ledger (%v): %v", ledgerPaths, err)
return nil, err
}
@@ -245,7 +255,8 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd
node.ledgerService = rpcs.MakeLedgerService(cfg, node.ledger, p2pNode, node.genesisID)
rpcs.RegisterTxService(node.transactionPool, p2pNode, node.genesisID, cfg.TxPoolSize, cfg.TxSyncServeResponseSize)
- crashPathname := filepath.Join(genesisDir, config.CrashFilename)
+ // crash data is stored in the cold data directory unless otherwise specified
+ crashPathname := filepath.Join(node.genesisDirs.CrashGenesisDir, config.CrashFilename)
crashAccess, err := db.MakeAccessor(crashPathname, false, false)
if err != nil {
log.Errorf("Cannot load crash data: %v", err)
@@ -254,12 +265,13 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd
blockValidator := blockValidatorImpl{l: node.ledger, verificationPool: node.highPriorityCryptoVerificationPool}
agreementLedger := makeAgreementLedger(node.ledger, node.net)
- var agreementClock timers.Clock
+ var agreementClock timers.Clock[agreement.TimeoutType]
if node.devMode {
- agreementClock = timers.MakeFrozenClock()
+ agreementClock = timers.MakeFrozenClock[agreement.TimeoutType]()
} else {
- agreementClock = timers.MakeMonotonicClock(time.Now())
+ agreementClock = timers.MakeMonotonicClock[agreement.TimeoutType](time.Now())
}
+
agreementParameters := agreement.Parameters{
Logger: log,
Accessor: crashAccess,
@@ -283,7 +295,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd
node.catchupService = catchup.MakeService(node.log, node.config, p2pNode, node.ledger, node.catchupBlockAuth, agreementLedger.UnmatchedPendingCertificates, node.lowPriorityCryptoVerificationPool)
node.txPoolSyncerService = rpcs.MakeTxSyncer(node.transactionPool, node.net, node.txHandler.SolicitedTxHandler(), time.Duration(cfg.TxSyncIntervalSeconds)*time.Second, time.Duration(cfg.TxSyncTimeoutSeconds)*time.Second, cfg.TxSyncServeResponseSize)
- registry, err := ensureParticipationDB(genesisDir, node.log)
+ registry, err := ensureParticipationDB(node.genesisDirs.ColdGenesisDir, node.log)
if err != nil {
log.Errorf("unable to initialize the participation registry database: %v", err)
return nil, err
@@ -316,7 +328,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd
node.tracer = messagetracer.NewTracer(log).Init(cfg)
gossip.SetTrace(agreementParameters.Network, node.tracer)
- node.stateProofWorker = stateproof.NewWorker(genesisDir, node.log, node.accountManager, node.ledger.Ledger, node.net, node)
+ node.stateProofWorker = stateproof.NewWorker(node.genesisDirs.StateproofGenesisDir, node.log, node.accountManager, node.ledger.Ledger, node.net, node)
return node, err
}
@@ -422,7 +434,7 @@ func (node *AlgorandFullNode) Stop() {
// note: unlike the other two functions, this accepts a whole filename
func (node *AlgorandFullNode) getExistingPartHandle(filename string) (db.Accessor, error) {
- filename = filepath.Join(node.rootDir, node.genesisID, filename)
+ filename = filepath.Join(node.genesisDirs.RootGenesisDir, filename)
_, err := os.Stat(filename)
if err == nil {
@@ -826,9 +838,7 @@ func (node *AlgorandFullNode) RemoveParticipationKey(partKeyID account.Participa
return account.ErrParticipationIDNotFound
}
- genID := node.GenesisID()
-
- outDir := filepath.Join(node.rootDir, genID)
+ outDir := node.genesisDirs.RootGenesisDir
filename := config.PartKeyFilename(partRecord.ParticipationID.String(), uint64(partRecord.FirstValid), uint64(partRecord.LastValid))
fullyQualifiedFilename := filepath.Join(outDir, filepath.Base(filename))
@@ -890,9 +900,7 @@ func createTemporaryParticipationKey(outDir string, partKeyBinary []byte) (strin
// InstallParticipationKey Given a participation key binary stream install the participation key.
func (node *AlgorandFullNode) InstallParticipationKey(partKeyBinary []byte) (account.ParticipationID, error) {
- genID := node.GenesisID()
-
- outDir := filepath.Join(node.rootDir, genID)
+ outDir := node.genesisDirs.RootGenesisDir
fullyQualifiedTempFile, err := createTemporaryParticipationKey(outDir, partKeyBinary)
// We need to make sure no tempfile is created/remains if there is an error
@@ -947,7 +955,7 @@ func (node *AlgorandFullNode) InstallParticipationKey(partKeyBinary []byte) (acc
func (node *AlgorandFullNode) loadParticipationKeys() error {
// Generate a list of all potential participation key files
- genesisDir := filepath.Join(node.rootDir, node.genesisID)
+ genesisDir := node.genesisDirs.RootGenesisDir
files, err := os.ReadDir(genesisDir)
if err != nil {
return fmt.Errorf("AlgorandFullNode.loadPartitipationKeys: could not read directory %v: %v", genesisDir, err)
diff --git a/node/node_test.go b/node/node_test.go
index 64e285eac1..c905fa78da 100644
--- a/node/node_test.go
+++ b/node/node_test.go
@@ -516,6 +516,165 @@ func TestMismatchingGenesisDirectoryPermissions(t *testing.T) {
require.NoError(t, os.RemoveAll(testDirectroy))
}
+// TestDefaultResourcePaths confirms that when no extra configuration is provided, all resources are created in the dataDir
+func TestDefaultResourcePaths(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ testDirectory := t.TempDir()
+
+ genesis := bookkeeping.Genesis{
+ SchemaID: "gen",
+ Proto: protocol.ConsensusCurrentVersion,
+ Network: config.Devtestnet,
+ FeeSink: sinkAddr.String(),
+ RewardsPool: poolAddr.String(),
+ }
+
+ cfg := config.GetDefaultLocal()
+
+ // the logger is set up by the server, so we don't test this here
+ log := logging.Base()
+
+ n, err := MakeFull(log, testDirectory, cfg, []string{}, genesis)
+
+ n.Start()
+ defer n.Stop()
+
+ require.NoError(t, err)
+
+ // confirm genesis dir exists in the data dir, and that resources exist in the expected locations
+ require.DirExists(t, filepath.Join(testDirectory, genesis.ID()))
+
+ _, err = os.Stat(filepath.Join(testDirectory, genesis.ID(), "ledger.tracker.sqlite"))
+ require.NoError(t, err)
+ _, err = os.Stat(filepath.Join(testDirectory, genesis.ID(), "stateproof.sqlite"))
+ require.NoError(t, err)
+ _, err = os.Stat(filepath.Join(testDirectory, genesis.ID(), "ledger.block.sqlite"))
+ require.NoError(t, err)
+ _, err = os.Stat(filepath.Join(testDirectory, genesis.ID(), "partregistry.sqlite"))
+ require.NoError(t, err)
+ _, err = os.Stat(filepath.Join(testDirectory, genesis.ID(), "crash.sqlite"))
+ require.NoError(t, err)
+}
+
+// TestConfiguredDataDirs tests to see that when HotDataDir and ColdDataDir are set, underlying resources are created in the correct locations
+// Not all resources are tested here, because not all resources use the paths provided to them immediately. For example, catchpoint only creates
+// a directory when writing a catchpoint file, which is not being done here with this simple node
+func TestConfiguredDataDirs(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ testDirectory := t.TempDir()
+ testDirHot := t.TempDir()
+ testDirCold := t.TempDir()
+
+ genesis := bookkeeping.Genesis{
+ SchemaID: "go-test-node-genesis",
+ Proto: protocol.ConsensusCurrentVersion,
+ Network: config.Devtestnet,
+ FeeSink: sinkAddr.String(),
+ RewardsPool: poolAddr.String(),
+ }
+
+ cfg := config.GetDefaultLocal()
+
+ cfg.HotDataDir = testDirHot
+ cfg.ColdDataDir = testDirCold
+ cfg.CatchpointTracking = 2
+ cfg.CatchpointInterval = 1
+
+ // the logger is set up by the server, so we don't test this here
+ log := logging.Base()
+
+ n, err := MakeFull(log, testDirectory, cfg, []string{}, genesis)
+ require.NoError(t, err)
+
+ n.Start()
+ defer n.Stop()
+
+ // confirm hot data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirHot, genesis.ID()))
+
+ // confirm the tracker is in the genesis dir of hot data dir
+ require.FileExists(t, filepath.Join(testDirHot, genesis.ID(), "ledger.tracker.sqlite"))
+
+ // confirm the stateproof db in the genesis dir of hot data dir
+ require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "stateproof.sqlite"))
+
+ // confirm cold data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirCold, genesis.ID()))
+
+ // confirm the blockdb is in the genesis dir of cold data dir
+ require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "ledger.block.sqlite"))
+
+ // confirm the partregistry is in the genesis dir of cold data dir
+ require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "partregistry.sqlite"))
+
+ // confirm the partregistry is in the genesis dir of cold data dir
+ require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "crash.sqlite"))
+}
+
+// TestConfiguredResourcePaths tests to see that when TrackerDbFilePath, BlockDbFilePath, StateproofDir, and CrashFilePath are set, underlying resources are created in the correct locations
+func TestConfiguredResourcePaths(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ testDirectory := t.TempDir()
+ testDirHot := t.TempDir()
+ testDirCold := t.TempDir()
+
+ // add a path for each resource now
+ trackerPath := filepath.Join(testDirectory, "custom_tracker")
+ blockPath := filepath.Join(testDirectory, "custom_block")
+ stateproofDir := filepath.Join(testDirectory, "custom_stateproof")
+ crashPath := filepath.Join(testDirectory, "custom_crash")
+
+ genesis := bookkeeping.Genesis{
+ SchemaID: "go-test-node-genesis",
+ Proto: protocol.ConsensusCurrentVersion,
+ Network: config.Devtestnet,
+ FeeSink: sinkAddr.String(),
+ RewardsPool: poolAddr.String(),
+ }
+
+ cfg := config.GetDefaultLocal()
+
+ cfg.HotDataDir = testDirHot
+ cfg.ColdDataDir = testDirCold
+ cfg.TrackerDBDir = trackerPath
+ cfg.BlockDBDir = blockPath
+ cfg.StateproofDir = stateproofDir
+ cfg.CrashDBDir = crashPath
+
+ // the logger is set up by the server, so we don't test this here
+ log := logging.Base()
+
+ n, err := MakeFull(log, testDirectory, cfg, []string{}, genesis)
+ require.NoError(t, err)
+
+ n.Start()
+ defer n.Stop()
+
+ // confirm hot data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirHot, genesis.ID()))
+
+ // the tracker shouldn't be in the hot data dir, but rather the custom path's genesis dir
+ require.NoFileExists(t, filepath.Join(testDirHot, genesis.ID(), "ledger.tracker.sqlite"))
+ require.FileExists(t, filepath.Join(cfg.TrackerDBDir, genesis.ID(), "ledger.tracker.sqlite"))
+
+ // same with stateproofs
+ require.NoFileExists(t, filepath.Join(testDirHot, genesis.ID(), "stateproof.sqlite"))
+ require.FileExists(t, filepath.Join(cfg.StateproofDir, genesis.ID(), "stateproof.sqlite"))
+
+ // confirm cold data dir exists and contains a genesis dir
+ require.DirExists(t, filepath.Join(testDirCold, genesis.ID()))
+
+ // block db shouldn't be in the cold data dir, but rather the custom path's genesis dir
+ require.NoFileExists(t, filepath.Join(testDirCold, genesis.ID(), "ledger.block.sqlite"))
+ require.FileExists(t, filepath.Join(cfg.BlockDBDir, genesis.ID(), "ledger.block.sqlite"))
+
+ require.NoFileExists(t, filepath.Join(testDirCold, genesis.ID(), "crash.sqlite"))
+ require.FileExists(t, filepath.Join(cfg.CrashDBDir, genesis.ID(), "crash.sqlite"))
+}
+
// TestOfflineOnlineClosedBitStatus a test that validates that the correct bits are being set
func TestOfflineOnlineClosedBitStatus(t *testing.T) {
partitiontest.PartitionTest(t)
diff --git a/package-deploy.yaml b/package-deploy.yaml
index 4ad27fb35f..871d6ef1f7 100644
--- a/package-deploy.yaml
+++ b/package-deploy.yaml
@@ -45,8 +45,8 @@ agents:
workDir: $HOME/projects/go-algorand
- name: rpm
- dockerFilePath: docker/build/cicd.centos.Dockerfile
- image: algorand/go-algorand-ci-linux-centos
+ dockerFilePath: docker/build/cicd.centos8.Dockerfile
+ image: algorand/go-algorand-ci-linux-centos8
version: scripts/configure_dev-deps.sh
buildArgs:
- GOLANG_VERSION=`./scripts/get_golang_version.sh`
@@ -54,9 +54,10 @@ agents:
- AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
- NETWORK=$NETWORK
- - PACKAGES_DIR=$PACKAGES_DIR
- NO_DEPLOY=$NO_DEPLOY
+ - PACKAGES_DIR=$PACKAGES_DIR
- S3_SOURCE=$S3_SOURCE
+ - STAGING=$STAGING
- VERSION=$VERSION
volumes:
- $XDG_RUNTIME_DIR/gnupg/S.gpg-agent:/root/.gnupg/S.gpg-agent
diff --git a/protocol/codec.go b/protocol/codec.go
index e0386eb9b2..62abaedad6 100644
--- a/protocol/codec.go
+++ b/protocol/codec.go
@@ -288,21 +288,41 @@ func (d *MsgpDecoderBytes) Remaining() int {
// encodingPool holds temporary byte slice buffers used for encoding messages.
var encodingPool = sync.Pool{
New: func() interface{} {
- return []byte{}
+ return &EncodingBuf{b: make([]byte, 0)}
},
}
+// EncodingBuf is a wrapper for a byte slice that can be used for encoding
+type EncodingBuf struct {
+ b []byte
+}
+
+// Bytes returns the underlying byte slice
+func (eb *EncodingBuf) Bytes() []byte {
+ return eb.b
+}
+
+// Update updates the underlying byte slice to the given one if its capacity exceeds the current one.
+func (eb *EncodingBuf) Update(v []byte) *EncodingBuf {
+ if cap(eb.b) < cap(v) {
+ eb.b = v
+ }
+ return eb
+}
+
// GetEncodingBuf returns a byte slice that can be used for encoding a
// temporary message. The byte slice has zero length but potentially
// non-zero capacity. The caller gets full ownership of the byte slice,
// but is encouraged to return it using PutEncodingBuf().
-func GetEncodingBuf() []byte {
- return encodingPool.Get().([]byte)[:0]
+func GetEncodingBuf() *EncodingBuf {
+ buf := encodingPool.Get().(*EncodingBuf)
+ buf.b = buf.b[:0]
+ return buf
}
// PutEncodingBuf places a byte slice into the pool of temporary buffers
// for encoding. The caller gives up ownership of the byte slice when
// passing it to PutEncodingBuf().
-func PutEncodingBuf(s []byte) {
- encodingPool.Put(s)
+func PutEncodingBuf(buf *EncodingBuf) {
+ encodingPool.Put(buf)
}
diff --git a/protocol/msgp_gen.go b/protocol/msgp_gen.go
index db191f018d..d2c7c1a2e1 100644
--- a/protocol/msgp_gen.go
+++ b/protocol/msgp_gen.go
@@ -11,6 +11,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -20,6 +21,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -29,6 +31,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -38,6 +41,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -47,6 +51,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -56,6 +61,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -65,6 +71,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -87,7 +94,12 @@ func (_ ConsensusVersion) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *ConsensusVersion) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *ConsensusVersion) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
{
var zb0001 string
var zb0002 int
@@ -111,6 +123,9 @@ func (z *ConsensusVersion) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *ConsensusVersion) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *ConsensusVersion) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*ConsensusVersion)
return ok
@@ -149,7 +164,12 @@ func (_ Error) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *Error) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *Error) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
@@ -163,6 +183,9 @@ func (z *Error) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *Error) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *Error) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*Error)
return ok
@@ -201,7 +224,12 @@ func (_ HashID) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *HashID) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *HashID) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
@@ -215,6 +243,9 @@ func (z *HashID) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *HashID) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *HashID) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*HashID)
return ok
@@ -253,7 +284,12 @@ func (_ NetworkID) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *NetworkID) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *NetworkID) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
@@ -267,6 +303,9 @@ func (z *NetworkID) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *NetworkID) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *NetworkID) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*NetworkID)
return ok
@@ -305,7 +344,12 @@ func (_ StateProofType) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *StateProofType) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *StateProofType) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
{
var zb0001 uint64
zb0001, bts, err = msgp.ReadUint64Bytes(bts)
@@ -319,6 +363,9 @@ func (z *StateProofType) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *StateProofType) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *StateProofType) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*StateProofType)
return ok
@@ -357,7 +404,12 @@ func (_ Tag) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *Tag) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *Tag) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
{
var zb0001 string
zb0001, bts, err = msgp.ReadStringBytes(bts)
@@ -371,6 +423,9 @@ func (z *Tag) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *Tag) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *Tag) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*Tag)
return ok
@@ -409,7 +464,12 @@ func (_ TxType) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *TxType) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *TxType) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
{
var zb0001 string
var zb0002 int
@@ -433,6 +493,9 @@ func (z *TxType) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *TxType) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *TxType) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*TxType)
return ok
diff --git a/protocol/test/msgp_gen.go b/protocol/test/msgp_gen.go
index 518afb1cb7..b7500acdd9 100644
--- a/protocol/test/msgp_gen.go
+++ b/protocol/test/msgp_gen.go
@@ -11,6 +11,7 @@ import (
// |-----> MarshalMsg
// |-----> CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> Msgsize
// |-----> MsgIsZero
@@ -40,7 +41,12 @@ func (_ testSlice) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *testSlice) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *testSlice) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
var zb0002 int
var zb0003 bool
zb0002, zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
@@ -71,6 +77,9 @@ func (z *testSlice) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *testSlice) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *testSlice) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*testSlice)
return ok
diff --git a/rpcs/blockService.go b/rpcs/blockService.go
index 0f48f873c0..2d4a4b822e 100644
--- a/rpcs/blockService.go
+++ b/rpcs/blockService.go
@@ -54,6 +54,9 @@ const blockResponseRetryAfter = "3"
const blockServerMaxBodyLength = 512 // we don't really pass meaningful content here, so 512 bytes should be a safe limit
const blockServerCatchupRequestBufferSize = 10
+// BlockResponseLatestRoundHeader is returned in the response header when the requested block is not available
+const BlockResponseLatestRoundHeader = "X-Latest-Round"
+
// BlockServiceBlockPath is the path to register BlockService as a handler for when using gorilla/mux
// e.g. .Handle(BlockServiceBlockPath, &ls)
const BlockServiceBlockPath = "/v{version:[0-9.]+}/{genesisID}/block/{round:[0-9a-z]+}"
@@ -65,6 +68,7 @@ const (
BlockDataKey = "blockData" // Block-data topic-key in the response
CertDataKey = "certData" // Cert-data topic-key in the response
BlockAndCertValue = "blockAndCert" // block+cert request data (as the value of requestDataTypeKey)
+ LatestRoundKey = "latest"
)
var errBlockServiceClosed = errors.New("block service is shutting down")
@@ -104,7 +108,7 @@ type BlockService struct {
closeWaitGroup sync.WaitGroup
mu deadlock.Mutex
memoryUsed uint64
- wsMemoryUsed uint64
+ wsMemoryUsed atomic.Uint64
memoryCap uint64
}
@@ -239,12 +243,13 @@ func (bs *BlockService) ServeHTTP(response http.ResponseWriter, request *http.Re
}
encodedBlockCert, err := bs.rawBlockBytes(basics.Round(round))
if err != nil {
- switch err.(type) {
+ switch lerr := err.(type) {
case ledgercore.ErrNoEntry:
// entry cound not be found.
ok := bs.redirectRequest(round, response, request)
if !ok {
response.Header().Set("Cache-Control", blockResponseMissingBlockCacheControl)
+ response.Header().Set(BlockResponseLatestRoundHeader, fmt.Sprintf("%d", lerr.Latest))
response.WriteHeader(http.StatusNotFound)
}
return
@@ -320,9 +325,9 @@ func (bs *BlockService) handleCatchupReq(ctx context.Context, reqMsg network.Inc
outMsg := network.OutgoingMessage{Topics: respTopics}
if n > 0 {
outMsg.OnRelease = func() {
- atomic.AddUint64(&bs.wsMemoryUsed, ^uint64(n-1))
+ bs.wsMemoryUsed.Add(^uint64(n - 1))
}
- atomic.AddUint64(&bs.wsMemoryUsed, (n))
+ bs.wsMemoryUsed.Add(n)
}
err := target.Respond(ctx, reqMsg, outMsg)
if err != nil {
@@ -332,7 +337,7 @@ func (bs *BlockService) handleCatchupReq(ctx context.Context, reqMsg network.Inc
// If we are over-capacity, we will not process the request
// respond to sender with error message
- memUsed := atomic.LoadUint64(&bs.wsMemoryUsed)
+ memUsed := bs.wsMemoryUsed.Load()
if memUsed > bs.memoryCap {
err := errMemoryAtCapacity{capacity: bs.memoryCap, used: memUsed}
bs.log.Infof("BlockService handleCatchupReq: %s", err.Error())
@@ -456,8 +461,12 @@ func (bs *BlockService) rawBlockBytes(round basics.Round) ([]byte, error) {
func topicBlockBytes(log logging.Logger, dataLedger LedgerForBlockService, round basics.Round, requestType string) (network.Topics, uint64) {
blk, cert, err := dataLedger.EncodedBlockCert(round)
if err != nil {
- switch err.(type) {
+ switch lerr := err.(type) {
case ledgercore.ErrNoEntry:
+ return network.Topics{
+ network.MakeTopic(network.ErrorKey, []byte(blockNotAvailableErrMsg)),
+ network.MakeTopic(LatestRoundKey, binary.BigEndian.AppendUint64([]byte{}, uint64(lerr.Latest))),
+ }, 0
default:
log.Infof("BlockService topicBlockBytes: %s", err)
}
diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go
index 6b275a2489..832b59c55b 100644
--- a/rpcs/blockService_test.go
+++ b/rpcs/blockService_test.go
@@ -143,6 +143,9 @@ func TestRedirectFallbackArchiver(t *testing.T) {
net2 := &httpTestPeerSource{}
config := config.GetDefaultLocal()
+ // Need to enable block service fallbacks
+ config.EnableBlockServiceFallbackToArchiver = true
+
bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID")
bs2 := MakeBlockService(log, config, ledger2, net2, "test-genesis-ID")
@@ -311,6 +314,8 @@ func TestRedirectOnFullCapacity(t *testing.T) {
net2 := &httpTestPeerSource{}
config := config.GetDefaultLocal()
+ // Need to enable block service fallbacks
+ config.EnableBlockServiceFallbackToArchiver = true
bs1 := MakeBlockService(log1, config, ledger1, net1, "test-genesis-ID")
bs2 := MakeBlockService(log2, config, ledger2, net2, "test-genesis-ID")
// set the memory cap so that it can serve only 1 block at a time
@@ -447,7 +452,7 @@ func TestWsBlockLimiting(t *testing.T) {
roundBin),
}
reqMsg.Data = topics.MarshallTopics()
- require.Zero(t, bs1.wsMemoryUsed)
+ require.Zero(t, bs1.wsMemoryUsed.Load())
bs1.handleCatchupReq(context.Background(), reqMsg)
// We should have received the message into the mock peer and the block service should have memoryUsed > 0
data, found := peer.responseTopics.GetValue(BlockDataKey)
@@ -455,7 +460,7 @@ func TestWsBlockLimiting(t *testing.T) {
blk, _, err := ledger.EncodedBlockCert(basics.Round(2))
require.NoError(t, err)
require.Equal(t, data, blk)
- require.Positive(t, bs1.wsMemoryUsed)
+ require.Positive(t, bs1.wsMemoryUsed.Load())
// Before making a new request save the callback since the new failed message will overwrite it in the mock peer
callback := peer.outMsg.OnRelease
@@ -469,7 +474,7 @@ func TestWsBlockLimiting(t *testing.T) {
// Now call the callback to free up memUsed
require.Nil(t, peer.outMsg.OnRelease)
callback()
- require.Zero(t, bs1.wsMemoryUsed)
+ require.Zero(t, bs1.wsMemoryUsed.Load())
}
// TestRedirectExceptions tests exception cases:
@@ -487,6 +492,9 @@ func TestRedirectExceptions(t *testing.T) {
net1 := &httpTestPeerSource{}
config := config.GetDefaultLocal()
+ // Need to enable block service fallbacks
+ config.EnableBlockServiceFallbackToArchiver = true
+
bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}")
nodeA := &basicRPCNode{}
@@ -543,8 +551,9 @@ func makeLedger(t *testing.T, namePostfix string) *data.Ledger {
cfg := config.GetDefaultLocal()
const inMem = true
+ prefix := t.Name() + namePostfix
ledger, err := data.LoadLedger(
- log, t.Name()+namePostfix, inMem, protocol.ConsensusCurrentVersion, genBal, "", genHash,
+ log, prefix, inMem, protocol.ConsensusCurrentVersion, genBal, "", genHash,
nil, cfg,
)
require.NoError(t, err)
diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go
index 8abf87e3ba..a3ff63e90e 100644
--- a/rpcs/ledgerService.go
+++ b/rpcs/ledgerService.go
@@ -63,7 +63,7 @@ type LedgerForService interface {
// LedgerService represents the Ledger RPC API
type LedgerService struct {
// running is non-zero once the service is running, and zero when it's not running. it needs to be at a 32-bit aligned address for RasPI support.
- running int32
+ running atomic.Int32
ledger LedgerForService
genesisID string
net network.GossipNode
@@ -89,14 +89,14 @@ func MakeLedgerService(config config.Local, ledger LedgerForService, net network
// Start listening to catchup requests
func (ls *LedgerService) Start() {
if ls.enableService {
- atomic.StoreInt32(&ls.running, 1)
+ ls.running.Store(1)
}
}
// Stop servicing catchup requests
func (ls *LedgerService) Stop() {
if ls.enableService {
- atomic.StoreInt32(&ls.running, 0)
+ ls.running.Store(0)
ls.stopping.Wait()
}
}
@@ -107,7 +107,7 @@ func (ls *LedgerService) Stop() {
func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.Request) {
ls.stopping.Add(1)
defer ls.stopping.Done()
- if atomic.AddInt32(&ls.running, 0) == 0 {
+ if ls.running.Add(0) == 0 {
response.WriteHeader(http.StatusNotFound)
return
}
diff --git a/rpcs/ledgerService_test.go b/rpcs/ledgerService_test.go
index 6b01cf0e16..1285795d4c 100644
--- a/rpcs/ledgerService_test.go
+++ b/rpcs/ledgerService_test.go
@@ -82,7 +82,7 @@ func TestLedgerService(t *testing.T) {
ledgerService := MakeLedgerService(cfg, &l, &fnet, genesisID)
fnet.AssertNotCalled(t, "RegisterHTTPHandler", LedgerServiceLedgerPath, ledgerService)
ledgerService.Start()
- require.Equal(t, int32(0), ledgerService.running)
+ require.Equal(t, int32(0), ledgerService.running.Load())
// Test GET 404
rr := httptest.NewRecorder()
@@ -97,7 +97,7 @@ func TestLedgerService(t *testing.T) {
ledgerService = MakeLedgerService(cfg, &l, &fnet, genesisID)
fnet.AssertCalled(t, "RegisterHTTPHandler", LedgerServiceLedgerPath, ledgerService)
ledgerService.Start()
- require.Equal(t, int32(1), ledgerService.running)
+ require.Equal(t, int32(1), ledgerService.running.Load())
// Test GET 400 Bad Version String
rr = httptest.NewRecorder()
@@ -170,5 +170,5 @@ func TestLedgerService(t *testing.T) {
// Test LedgerService Stopped
ledgerService.Stop()
- require.Equal(t, int32(0), ledgerService.running)
+ require.Equal(t, int32(0), ledgerService.running.Load())
}
diff --git a/rpcs/msgp_gen.go b/rpcs/msgp_gen.go
index 5f8af433f7..4e091781cd 100644
--- a/rpcs/msgp_gen.go
+++ b/rpcs/msgp_gen.go
@@ -14,6 +14,7 @@ import (
// |-----> (*) MarshalMsg
// |-----> (*) CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> (*) Msgsize
// |-----> (*) MsgIsZero
@@ -39,7 +40,12 @@ func (_ *EncodedBlockCert) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *EncodedBlockCert) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *EncodedBlockCert) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
var field []byte
_ = field
var zb0001 int
@@ -53,7 +59,7 @@ func (z *EncodedBlockCert) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).Block.UnmarshalMsg(bts)
+ bts, err = (*z).Block.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Block")
return
@@ -61,7 +67,7 @@ func (z *EncodedBlockCert) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).Certificate.UnmarshalMsg(bts)
+ bts, err = (*z).Certificate.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Certificate")
return
@@ -91,13 +97,13 @@ func (z *EncodedBlockCert) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
switch string(field) {
case "block":
- bts, err = (*z).Block.UnmarshalMsg(bts)
+ bts, err = (*z).Block.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Block")
return
}
case "cert":
- bts, err = (*z).Certificate.UnmarshalMsg(bts)
+ bts, err = (*z).Certificate.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Certificate")
return
@@ -115,6 +121,9 @@ func (z *EncodedBlockCert) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *EncodedBlockCert) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *EncodedBlockCert) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*EncodedBlockCert)
return ok
diff --git a/rpcs/txService_test.go b/rpcs/txService_test.go
index dd999d6e65..8ef49e45a6 100644
--- a/rpcs/txService_test.go
+++ b/rpcs/txService_test.go
@@ -24,7 +24,6 @@ import (
"os"
"strings"
"sync"
- "sync/atomic"
"testing"
"time"
@@ -153,7 +152,7 @@ func TestTxSync(t *testing.T) {
// Since syncer is not Started, set the context here
syncer.ctx, syncer.cancel = context.WithCancel(context.Background())
require.NoError(t, syncer.sync())
- require.Equal(t, int32(3), atomic.LoadInt32(&handler.messageCounter))
+ require.Equal(t, int32(3), handler.messageCounter.Load())
}
func BenchmarkTxSync(b *testing.B) {
diff --git a/rpcs/txSyncer_test.go b/rpcs/txSyncer_test.go
index b05e050ee2..43e85f4523 100644
--- a/rpcs/txSyncer_test.go
+++ b/rpcs/txSyncer_test.go
@@ -103,12 +103,12 @@ func (mock mockPendingTxAggregate) PendingTxGroups() [][]transactions.SignedTxn
}
type mockHandler struct {
- messageCounter int32
+ messageCounter atomic.Int32
err error
}
func (handler *mockHandler) Handle(txgroup []transactions.SignedTxn) error {
- atomic.AddInt32(&handler.messageCounter, 1)
+ handler.messageCounter.Add(1)
return handler.err
}
@@ -201,7 +201,7 @@ func TestSyncFromClient(t *testing.T) {
syncer.log = logging.TestingLog(t)
require.NoError(t, syncer.syncFromClient(&client))
- require.Equal(t, int32(1), atomic.LoadInt32(&handler.messageCounter))
+ require.Equal(t, int32(1), handler.messageCounter.Load())
}
func TestSyncFromUnsupportedClient(t *testing.T) {
@@ -218,7 +218,7 @@ func TestSyncFromUnsupportedClient(t *testing.T) {
syncer.log = logging.TestingLog(t)
require.Error(t, syncer.syncFromClient(&client))
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
}
func TestSyncFromClientAndQuit(t *testing.T) {
@@ -235,7 +235,7 @@ func TestSyncFromClientAndQuit(t *testing.T) {
syncer.log = logging.TestingLog(t)
syncer.cancel()
require.Error(t, syncer.syncFromClient(&client))
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
}
func TestSyncFromClientAndError(t *testing.T) {
@@ -251,7 +251,7 @@ func TestSyncFromClientAndError(t *testing.T) {
syncer.ctx, syncer.cancel = context.WithCancel(context.Background())
syncer.log = logging.TestingLog(t)
require.Error(t, syncer.syncFromClient(&client))
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
}
func TestSyncFromClientAndTimeout(t *testing.T) {
@@ -268,7 +268,7 @@ func TestSyncFromClientAndTimeout(t *testing.T) {
syncer.ctx, syncer.cancel = context.WithCancel(context.Background())
syncer.log = logging.TestingLog(t)
require.Error(t, syncer.syncFromClient(&client))
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
}
func TestSync(t *testing.T) {
@@ -292,7 +292,7 @@ func TestSync(t *testing.T) {
syncer.log = logging.TestingLog(t)
require.NoError(t, syncer.sync())
- require.Equal(t, int32(1), atomic.LoadInt32(&handler.messageCounter))
+ require.Equal(t, int32(1), handler.messageCounter.Load())
}
func TestNoClientsSync(t *testing.T) {
@@ -307,7 +307,7 @@ func TestNoClientsSync(t *testing.T) {
syncer.log = logging.TestingLog(t)
require.NoError(t, syncer.sync())
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
}
func TestStartAndStop(t *testing.T) {
@@ -335,22 +335,22 @@ func TestStartAndStop(t *testing.T) {
canStart := make(chan struct{})
syncer.Start(canStart)
time.Sleep(2 * time.Second)
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
// signal that syncing can start
close(canStart)
for x := 0; x < 20; x++ {
time.Sleep(100 * time.Millisecond)
- if atomic.LoadInt32(&handler.messageCounter) != 0 {
+ if handler.messageCounter.Load() != 0 {
break
}
}
- require.Equal(t, int32(1), atomic.LoadInt32(&handler.messageCounter))
+ require.Equal(t, int32(1), handler.messageCounter.Load())
// stop syncing and ensure it doesn't happen
syncer.Stop()
time.Sleep(2 * time.Second)
- require.Equal(t, int32(1), atomic.LoadInt32(&handler.messageCounter))
+ require.Equal(t, int32(1), handler.messageCounter.Load())
}
func TestStartAndQuit(t *testing.T) {
@@ -370,12 +370,12 @@ func TestStartAndQuit(t *testing.T) {
canStart := make(chan struct{})
syncer.Start(canStart)
time.Sleep(2 * time.Second)
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
syncer.cancel()
time.Sleep(50 * time.Millisecond)
// signal that syncing can start, but ensure that it doesn't start (since we quit)
close(canStart)
time.Sleep(2 * time.Second)
- require.Zero(t, atomic.LoadInt32(&handler.messageCounter))
+ require.Zero(t, handler.messageCounter.Load())
}
diff --git a/scripts/buildtools/versions b/scripts/buildtools/versions
index f2f5401fe2..ba43b37f60 100644
--- a/scripts/buildtools/versions
+++ b/scripts/buildtools/versions
@@ -1,6 +1,6 @@
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
golang.org/x/tools v0.9.3
-github.com/algorand/msgp v1.1.55
+github.com/algorand/msgp v1.1.60
github.com/algorand/oapi-codegen v1.12.0-algorand.0
github.com/go-swagger/go-swagger v0.30.4
gotest.tools/gotestsum v1.10.0
diff --git a/scripts/check_deps.sh b/scripts/check_deps.sh
index a296c11b93..4022bb544b 100755
--- a/scripts/check_deps.sh
+++ b/scripts/check_deps.sh
@@ -48,6 +48,7 @@ check_go_binary_version() {
if [ "$expected_version" != "$actual_version" ]; then
echo "$YELLOW_FG[WARNING]$END_FG_COLOR $binary_name version mismatch, expected $expected_version, but got $actual_version"
+ echo "Use 'install_buildtools.sh' to fix."
fi
}
diff --git a/scripts/configure_dev.sh b/scripts/configure_dev.sh
index d40b551474..c7bd93a250 100755
--- a/scripts/configure_dev.sh
+++ b/scripts/configure_dev.sh
@@ -13,24 +13,30 @@ Options:
FORCE=false
while getopts ":sfh" opt; do
- case ${opt} in
- f ) FORCE=true
- ;;
- h ) echo "${HELP}"
+ case ${opt} in
+ f)
+ FORCE=true
+ ;;
+ h)
+ echo "${HELP}"
exit 0
- ;;
- \? ) echo "${HELP}"
+ ;;
+ \?)
+ echo "${HELP}"
exit 2
- ;;
- esac
+ ;;
+ esac
done
-SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
+SCRIPTPATH="$(
+ cd "$(dirname "$0")"
+ pwd -P
+)"
OS=$("$SCRIPTPATH"/ostype.sh)
function install_or_upgrade {
- if ${FORCE} ; then
+ if ${FORCE}; then
BREW_FORCE="-f"
fi
if brew ls --versions "$1" >/dev/null; then
@@ -43,30 +49,30 @@ function install_or_upgrade {
function install_windows_shellcheck() {
version="v0.7.1"
if ! wget https://github.com/koalaman/shellcheck/releases/download/$version/shellcheck-$version.zip -O /tmp/shellcheck-$version.zip; then
- rm /tmp/shellcheck-$version.zip &> /dev/null
+ rm /tmp/shellcheck-$version.zip &>/dev/null
echo "Error downloading shellcheck $version"
return 1
fi
if ! unzip -o /tmp/shellcheck-$version.zip shellcheck-$version.exe -d /tmp; then
- rm /tmp/shellcheck-$version.zip &> /dev/null
+ rm /tmp/shellcheck-$version.zip &>/dev/null
echo "Unable to decompress shellcheck $version"
return 1
fi
if ! mv -f /tmp/shellcheck-$version.exe /usr/bin/shellcheck.exe; then
- rm /tmp/shellcheck-$version.zip &> /dev/null
+ rm /tmp/shellcheck-$version.zip &>/dev/null
echo "Unable to move shellcheck to /usr/bin"
return 1
fi
- rm /tmp/shellcheck-$version.zip &> /dev/null
+ rm /tmp/shellcheck-$version.zip &>/dev/null
return 0
}
if [ "${OS}" = "linux" ]; then
- if ! which sudo > /dev/null; then
+ if ! which sudo >/dev/null; then
"$SCRIPTPATH/install_linux_deps.sh"
else
sudo "$SCRIPTPATH/install_linux_deps.sh"
@@ -74,7 +80,13 @@ if [ "${OS}" = "linux" ]; then
elif [ "${OS}" = "darwin" ]; then
if [ "${CIRCLECI}" != "true" ]; then
brew update
- brew tap homebrew/cask
+ brew_version=$(brew --version | head -1 | cut -d' ' -f2)
+ major_version=$(echo $brew_version | cut -d. -f1)
+ minor_version=$(echo $brew_version | cut -d. -f2)
+ version_decimal="$major_version.$minor_version"
+ if (($(echo "$version_decimal < 2.5" | bc -l))); then
+ brew tap homebrew/cask
+ fi
fi
install_or_upgrade pkg-config
install_or_upgrade libtool
diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh
index 331626f188..10bdb8630d 100755
--- a/scripts/get_golang_version.sh
+++ b/scripts/get_golang_version.sh
@@ -11,7 +11,7 @@
# Our build task-runner `mule` will refer to this script and will automatically
# build a new image whenever the version number has been changed.
-BUILD=1.20.6
+BUILD=1.20.7
MIN=1.20
GO_MOD_SUPPORT=1.20
diff --git a/scripts/release/mule/deploy/releases_page/generate_releases_page.py b/scripts/release/mule/deploy/releases_page/generate_releases_page.py
index 88f70c6384..07d7ce70a0 100755
--- a/scripts/release/mule/deploy/releases_page/generate_releases_page.py
+++ b/scripts/release/mule/deploy/releases_page/generate_releases_page.py
@@ -213,12 +213,19 @@ def main():
# 'releases/beta/f9fa9a084_2.5.2' => [file_obj1, file_obj2, ...]
release_sets = get_stage_release_set(staging_response)
+ release_contents = []
# List everything from the releases bucket s3://algorand-releases/
releases_response = s3.list_objects_v2(Bucket=releases_bucket)
+ release_contents.extend(releases_response["Contents"])
+
+ # If response was truncated, keep looping and appending
+ while releases_response["IsTruncated"] == True:
+ releases_response = s3.list_objects_v2(Bucket=releases_bucket, ContinuationToken=releases_response["NextContinuationToken"])
+ release_contents.extend(releases_response["Contents"])
# Return dict keyed by filename of file_objs from
# s3://algorand-releases/
- release_files = objects_by_fname(releases_response["Contents"])
+ release_files = objects_by_fname(release_contents)
table = []
diff --git a/scripts/release/mule/deploy/releases_page/html.tpl b/scripts/release/mule/deploy/releases_page/html.tpl
index c52f342d45..3c8f78bc9b 100644
--- a/scripts/release/mule/deploy/releases_page/html.tpl
+++ b/scripts/release/mule/deploy/releases_page/html.tpl
@@ -11,7 +11,7 @@
See Algorand Developer Resources for instructions on installation and getting started
The Algorand public key to verify these files (except RPM** ) is at https://releases.algorand.com/key.pub
The public key for verifying RPMs is https://releases.algorand.com/rpm/rpm_algorand.pub
-** The RPM package for the 2.0.3 release was signed with the https://releases.algorand.com/key.pub . All other releases will have been signed with the RPM key as noted.
+The public key for verifying binaries out of our CI builds is https://releases.algorand.com/dev_ci_build.pub
diff --git a/scripts/release/mule/deploy/rpm/deploy.sh b/scripts/release/mule/deploy/rpm/deploy.sh
index 1e9719df55..f660f1d01b 100755
--- a/scripts/release/mule/deploy/rpm/deploy.sh
+++ b/scripts/release/mule/deploy/rpm/deploy.sh
@@ -17,25 +17,25 @@ VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)}
NO_DEPLOY=${NO_DEPLOY:-false}
OS_TYPE=$(./scripts/release/mule/common/ostype.sh)
PACKAGES_DIR=${PACKAGES_DIR:-"./tmp/node_pkgs/$OS_TYPE/$ARCH_TYPE"}
+STAGING=${STAGING:-"algorand-staging/releases"}
if [ -n "$S3_SOURCE" ]
then
PREFIX="$S3_SOURCE/$CHANNEL/$VERSION"
-
- aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.x86_64.rpm" /root
- aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.x86_64.rpm" /root
+ if [ "$CHANNEL" == "beta" ]
+ then
+ aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.x86_64.rpm" /root
+ aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.x86_64.rpm" /root
+ else
+ aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.x86_64.rpm" /root
+ aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.x86_64.rpm" /root
+ fi
else
cp "$PACKAGES_DIR"/*"$VERSION"*.rpm /root
fi
pushd /root
-aws s3 cp s3://algorand-devops-misc/tools/gnupg2.2.9_centos7_amd64.tar.bz2 .
-tar jxf gnupg*.tar.bz2
-
-export PATH="/root/gnupg2/bin:$PATH"
-export LD_LIBRARY_PATH=/root/gnupg2/lib
-
mkdir -p .gnupg
chmod 400 .gnupg
touch .gnupg/gpg.conf
@@ -51,20 +51,14 @@ echo "wat" | gpg -u rpm@algorand.com --clearsign
cat << EOF > .rpmmacros
%_gpg_name Algorand RPM
-%__gpg /root/gnupg2/bin/gpg
+%__gpg /usr/bin/gpg2
%__gpg_check_password_cmd true
EOF
-cat << EOF > rpmsign.py
-import rpm
-import sys
-rpm.addSign(sys.argv[1], '')
-EOF
-
mkdir rpmrepo
for rpm in $(ls *"$VERSION"*.rpm)
do
- python2 rpmsign.py "$rpm"
+ rpmsign --addsign "$rpm"
cp -p "$rpm" rpmrepo
done
@@ -78,6 +72,8 @@ then
cp -r /root/rpmrepo .
else
aws s3 sync rpmrepo "s3://algorand-releases/rpm/$CHANNEL/"
+ # sync signatures to releases so that the .sig files load from there
+ aws s3 sync s3://$STAGING/releases/$CHANNEL/ s3://algorand-releases/rpm/sigs/$CHANNEL/ --exclude='*' --include='*.rpm.sig'
fi
echo
diff --git a/scripts/upload_config.sh b/scripts/upload_config.sh
index 6ada497d2b..e075cccfdc 100755
--- a/scripts/upload_config.sh
+++ b/scripts/upload_config.sh
@@ -34,6 +34,9 @@ SRCPATH=${SCRIPTPATH}/..
export CHANNEL=$2
export FULLVERSION=$($SRCPATH/scripts/compute_build_number.sh -f)
+# prevent ._* files from being included in the tarball
+export COPYFILE_DISABLE=true
+
TEMPDIR=$(mktemp -d -t "upload_config.tmp.XXXXXX")
TARFILE=${TEMPDIR}/config_${CHANNEL}_${FULLVERSION}.tar.gz
diff --git a/shared/pingpong/accounts.go b/shared/pingpong/accounts.go
index 0471d84be4..3c34469f3b 100644
--- a/shared/pingpong/accounts.go
+++ b/shared/pingpong/accounts.go
@@ -209,10 +209,10 @@ func (pps *WorkerState) ensureAccounts(ac *libgoal.Client) (err error) {
}
ppa := &pingPongAccount{
- balance: amt,
- sk: secret,
- pk: accountAddress,
+ sk: secret,
+ pk: accountAddress,
}
+ ppa.balance.Store(amt)
pps.integrateAccountInfo(addr, ppa, ai)
@@ -246,7 +246,7 @@ func (pps *WorkerState) ensureAccounts(ac *libgoal.Client) (err error) {
}
func (pps *WorkerState) integrateAccountInfo(addr string, ppa *pingPongAccount, ai model.Account) {
- ppa.balance = ai.Amount
+ ppa.balance.Store(ai.Amount)
// assets this account has created
if ai.CreatedAssets != nil {
for _, ap := range *ai.CreatedAssets {
diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go
index a35fd451a4..95aeac0391 100644
--- a/shared/pingpong/pingpong.go
+++ b/shared/pingpong/pingpong.go
@@ -57,7 +57,7 @@ type CreatablesInfo struct {
// pingPongAccount represents the account state for each account in the pingpong application
// This includes the current balance and public/private keys tied to the account
type pingPongAccount struct {
- balance uint64
+ balance atomic.Uint64
balanceRound uint64
deadlock.Mutex
@@ -69,22 +69,22 @@ type pingPongAccount struct {
}
func (ppa *pingPongAccount) getBalance() uint64 {
- return atomic.LoadUint64(&ppa.balance)
+ return ppa.balance.Load()
}
func (ppa *pingPongAccount) setBalance(balance uint64) {
- atomic.StoreUint64(&ppa.balance, balance)
+ ppa.balance.Store(balance)
}
func (ppa *pingPongAccount) addBalance(offset int64) {
if offset >= 0 {
- atomic.AddUint64(&ppa.balance, uint64(offset))
+ ppa.balance.Add(uint64(offset))
return
}
for {
- v := atomic.LoadUint64(&ppa.balance)
+ v := ppa.balance.Load()
nv := v - uint64(-offset)
- done := atomic.CompareAndSwapUint64(&ppa.balance, v, nv)
+ done := ppa.balance.CompareAndSwap(v, nv)
if done {
return
}
@@ -118,7 +118,7 @@ func (ppa *pingPongAccount) String() string {
ppa.Lock()
defer ppa.Unlock()
var ow strings.Builder
- fmt.Fprintf(&ow, "%s %d", ppa.pk.String(), ppa.balance)
+ fmt.Fprintf(&ow, "%s %d", ppa.pk.String(), ppa.balance.Load())
if len(ppa.holdings) > 0 {
fmt.Fprintf(&ow, "[")
first := true
@@ -1036,11 +1036,11 @@ type paymentUpdate struct {
}
func (au *paymentUpdate) apply(pps *WorkerState) {
- pps.accounts[au.from].balance -= (au.fee + au.amt)
+ pps.accounts[au.from].balance.Add(-(au.fee + au.amt))
// update account balance
to := pps.accounts[au.to]
if to != nil {
- to.balance += au.amt
+ to.balance.Add(au.amt)
}
}
@@ -1164,7 +1164,7 @@ type assetUpdate struct {
}
func (au *assetUpdate) apply(pps *WorkerState) {
- pps.accounts[au.from].balance -= au.fee
+ pps.accounts[au.from].balance.Add(-au.fee)
pps.accounts[au.from].holdings[au.aidx] -= au.amt
to := pps.accounts[au.to]
if to.holdings == nil {
@@ -1240,7 +1240,7 @@ type appUpdate struct {
}
func (au *appUpdate) apply(pps *WorkerState) {
- pps.accounts[au.from].balance -= au.fee
+ pps.accounts[au.from].balance.Add(-au.fee)
}
func (pps *WorkerState) constructNFTGenTxn(from, to string, fee uint64, client *libgoal.Client, noteField []byte, lease [32]byte) (txn transactions.Transaction, sender string, update txnUpdate, err error) {
@@ -1323,7 +1323,7 @@ type nftgenUpdate struct {
}
func (au *nftgenUpdate) apply(pps *WorkerState) {
- pps.accounts[au.from].balance -= au.fee
+ pps.accounts[au.from].balance.Add(-au.fee)
}
func signTxn(signer *pingPongAccount, txn transactions.Transaction, cfg PpConfig) (stxn transactions.SignedTxn, err error) {
diff --git a/stateproof/msgp_gen.go b/stateproof/msgp_gen.go
index 11ad409337..f7a04efecd 100644
--- a/stateproof/msgp_gen.go
+++ b/stateproof/msgp_gen.go
@@ -19,6 +19,7 @@ import (
// |-----> (*) MarshalMsg
// |-----> (*) CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> (*) Msgsize
// |-----> (*) MsgIsZero
@@ -28,6 +29,7 @@ import (
// |-----> (*) MarshalMsg
// |-----> (*) CanMarshalMsg
// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
// |-----> (*) CanUnmarshalMsg
// |-----> (*) Msgsize
// |-----> (*) MsgIsZero
@@ -80,7 +82,12 @@ func (_ *sigFromAddr) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *sigFromAddr) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
var field []byte
_ = field
var zb0001 int
@@ -94,7 +101,7 @@ func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).SignerAddress.UnmarshalMsg(bts)
+ bts, err = (*z).SignerAddress.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "SignerAddress")
return
@@ -102,7 +109,7 @@ func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).Round.UnmarshalMsg(bts)
+ bts, err = (*z).Round.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Round")
return
@@ -110,7 +117,7 @@ func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0001 > 0 {
zb0001--
- bts, err = (*z).Sig.UnmarshalMsg(bts)
+ bts, err = (*z).Sig.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Sig")
return
@@ -140,19 +147,19 @@ func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
switch string(field) {
case "a":
- bts, err = (*z).SignerAddress.UnmarshalMsg(bts)
+ bts, err = (*z).SignerAddress.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "SignerAddress")
return
}
case "r":
- bts, err = (*z).Round.UnmarshalMsg(bts)
+ bts, err = (*z).Round.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Round")
return
}
case "s":
- bts, err = (*z).Sig.UnmarshalMsg(bts)
+ bts, err = (*z).Sig.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Sig")
return
@@ -170,6 +177,9 @@ func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *sigFromAddr) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*sigFromAddr)
return ok
@@ -266,7 +276,12 @@ func (_ *spProver) CanMarshalMsg(z interface{}) bool {
}
// UnmarshalMsg implements msgp.Unmarshaler
-func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
+func (z *spProver) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
var field []byte
_ = field
var zb0003 int
@@ -290,7 +305,7 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
if (*z).Prover == nil {
(*z).Prover = new(stateproof.Prover)
}
- bts, err = (*z).Prover.UnmarshalMsg(bts)
+ bts, err = (*z).Prover.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Prover")
return
@@ -320,7 +335,7 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 basics.Address
var zb0002 uint64
zb0005--
- bts, err = zb0001.UnmarshalMsg(bts)
+ bts, err = zb0001.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "AddrToPos")
return
@@ -335,7 +350,7 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0003 > 0 {
zb0003--
- bts, err = (*z).VotersHdr.UnmarshalMsg(bts)
+ bts, err = (*z).VotersHdr.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "VotersHdr")
return
@@ -343,7 +358,7 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
}
if zb0003 > 0 {
zb0003--
- bts, err = (*z).Message.UnmarshalMsg(bts)
+ bts, err = (*z).Message.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "struct-from-array", "Message")
return
@@ -383,7 +398,7 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
if (*z).Prover == nil {
(*z).Prover = new(stateproof.Prover)
}
- bts, err = (*z).Prover.UnmarshalMsg(bts)
+ bts, err = (*z).Prover.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Prover")
return
@@ -411,7 +426,7 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
var zb0001 basics.Address
var zb0002 uint64
zb0007--
- bts, err = zb0001.UnmarshalMsg(bts)
+ bts, err = zb0001.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "AddrToPos")
return
@@ -424,13 +439,13 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
(*z).AddrToPos[zb0001] = zb0002
}
case "hdr":
- bts, err = (*z).VotersHdr.UnmarshalMsg(bts)
+ bts, err = (*z).VotersHdr.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "VotersHdr")
return
}
case "msg":
- bts, err = (*z).Message.UnmarshalMsg(bts)
+ bts, err = (*z).Message.UnmarshalMsgWithState(bts, st)
if err != nil {
err = msgp.WrapError(err, "Message")
return
@@ -448,6 +463,9 @@ func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
+func (z *spProver) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
func (_ *spProver) CanUnmarshalMsg(z interface{}) bool {
_, ok := (z).(*spProver)
return ok
diff --git a/test/e2e-go/features/catchup/basicCatchup_test.go b/test/e2e-go/features/catchup/basicCatchup_test.go
index 7663e54b3e..f6a009dc19 100644
--- a/test/e2e-go/features/catchup/basicCatchup_test.go
+++ b/test/e2e-go/features/catchup/basicCatchup_test.go
@@ -17,6 +17,7 @@
package catchup
import (
+ "fmt"
"os"
"path/filepath"
"testing"
@@ -72,24 +73,6 @@ func TestBasicCatchup(t *testing.T) {
// Now, catch up
err = fixture.LibGoalFixture.ClientWaitForRoundWithTimeout(cloneClient, waitForRound)
a.NoError(err)
-
- cloneNC := fixture.GetNodeControllerForDataDir(cloneDataDir)
- cloneRestClient := fixture.GetAlgodClientForController(cloneNC)
-
- // an immediate call for ready will error, for sync time != 0
- a.Error(cloneRestClient.ReadyCheck())
-
- for {
- status, err := cloneRestClient.Status()
- a.NoError(err)
-
- if status.LastRound < 10 {
- time.Sleep(250 * time.Millisecond)
- continue
- }
- a.NoError(cloneRestClient.ReadyCheck())
- break
- }
}
// TestCatchupOverGossip tests catchup across network versions
@@ -98,20 +81,23 @@ func TestCatchupOverGossip(t *testing.T) {
partitiontest.PartitionTest(t)
defer fixtures.ShutdownSynchronizedTest(t)
- t.Parallel()
-
syncTest := fixtures.SynchronizedTest(t)
supportedVersions := network.SupportedProtocolVersions
require.LessOrEqual(syncTest, len(supportedVersions), 3)
+ subTest := func(tt *testing.T, ledgerVer, fetcherVer string) {
+ tt.Run(fmt.Sprintf("ledger=%s,fetcher=%s", ledgerVer, fetcherVer),
+ func(t *testing.T) { runCatchupOverGossip(t, ledgerVer, fetcherVer) })
+ }
+
// ledger node upgraded version, fetcher node upgraded version
// Run with the default values. Instead of "", pass the default value
// to exercise loading it from the config file.
runCatchupOverGossip(syncTest, supportedVersions[0], supportedVersions[0])
for i := 1; i < len(supportedVersions); i++ {
- runCatchupOverGossip(t, supportedVersions[i], "")
- runCatchupOverGossip(t, "", supportedVersions[i])
- runCatchupOverGossip(t, supportedVersions[i], supportedVersions[i])
+ subTest(t, supportedVersions[i], "")
+ subTest(t, "", supportedVersions[i])
+ subTest(t, supportedVersions[i], supportedVersions[i])
}
}
diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go
index ecbecbdb87..eb83451307 100644
--- a/test/e2e-go/features/catchup/catchpointCatchup_test.go
+++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go
@@ -316,7 +316,7 @@ func TestCatchpointCatchupFailure(t *testing.T) {
err = primaryNode.StopAlgod()
a.NoError(err)
- _, err = usingNodeRestClient.Catchup(catchpointLabel)
+ _, err = usingNodeRestClient.Catchup(catchpointLabel, 0)
a.ErrorContains(err, node.MakeStartCatchpointError(catchpointLabel, fmt.Errorf("")).Error())
}
@@ -358,11 +358,15 @@ func TestBasicCatchpointCatchup(t *testing.T) {
catchpointLabel := waitForCatchpointGeneration(t, fixture, primaryNodeRestClient, targetCatchpointRound)
- _, err = usingNodeRestClient.Catchup(catchpointLabel)
+ _, err = usingNodeRestClient.Catchup(catchpointLabel, 0)
a.NoError(err)
err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetCatchpointRound+1))
a.NoError(err)
+
+ // ensure the raw block can be downloaded (including cert)
+ _, err = usingNodeRestClient.RawBlock(uint64(targetCatchpointRound))
+ a.NoError(err)
}
func TestCatchpointLabelGeneration(t *testing.T) {
@@ -534,7 +538,7 @@ func TestNodeTxHandlerRestart(t *testing.T) {
lastCatchpoint := waitForCatchpointGeneration(t, &fixture, relayClient, basics.Round(targetCatchpointRound))
// let the primary node catchup
- err = client1.Catchup(lastCatchpoint)
+ _, err = client1.Catchup(lastCatchpoint, 0)
a.NoError(err)
status1, err := client1.Status()
@@ -647,7 +651,7 @@ func TestReadyEndpoint(t *testing.T) {
// Then when the primary node is at target round, it should satisfy ready 200 condition
// let the primary node catchup
- err = client1.Catchup(lastCatchpoint)
+ _, err = client1.Catchup(lastCatchpoint, 0)
a.NoError(err)
// The primary node is catching up with its previous catchpoint
@@ -789,7 +793,7 @@ func TestNodeTxSyncRestart(t *testing.T) {
_, err = fixture.StartNode(primaryNode.GetDataDir())
a.NoError(err)
// let the primary node catchup
- err = client1.Catchup(lastCatchpoint)
+ _, err = client1.Catchup(lastCatchpoint, 0)
a.NoError(err)
// the transaction should not be confirmed yet
diff --git a/test/e2e-go/features/catchup/stateproofsCatchup_test.go b/test/e2e-go/features/catchup/stateproofsCatchup_test.go
index 7f11ca37f5..4d3140c8d0 100644
--- a/test/e2e-go/features/catchup/stateproofsCatchup_test.go
+++ b/test/e2e-go/features/catchup/stateproofsCatchup_test.go
@@ -93,7 +93,7 @@ func TestStateProofInReplayCatchpoint(t *testing.T) {
catchpointLabel := waitForCatchpointGeneration(t, fixture, primaryNodeRestClient, targetCatchpointRound)
- _, err = usingNodeRestClient.Catchup(catchpointLabel)
+ _, err = usingNodeRestClient.Catchup(catchpointLabel, 0)
a.NoError(err)
// waiting for fastcatchup to start
@@ -169,7 +169,7 @@ func TestStateProofAfterCatchpoint(t *testing.T) {
catchpointLabel := waitForCatchpointGeneration(t, fixture, primaryNodeRestClient, targetCatchpointRound)
- _, err = usingNodeRestClient.Catchup(catchpointLabel)
+ _, err = usingNodeRestClient.Catchup(catchpointLabel, 0)
a.NoError(err)
roundAfterSPGeneration := targetCatchpointRound.RoundUpToMultipleOf(basics.Round(consensusParams.StateProofInterval)) +
@@ -258,7 +258,7 @@ func TestSendSigsAfterCatchpointCatchup(t *testing.T) {
targetCatchpointRound := getFirstCatchpointRound(&consensusParams)
catchpointLabel := waitForCatchpointGeneration(t, &fixture, primaryNodeRestClient, targetCatchpointRound)
- _, err = usingNodeRestClient.Catchup(catchpointLabel)
+ _, err = usingNodeRestClient.Catchup(catchpointLabel, 0)
a.NoError(err)
err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetCatchpointRound)+1)
diff --git a/test/e2e-go/features/p2p/p2p_basic_test.go b/test/e2e-go/features/p2p/p2p_basic_test.go
new file mode 100644
index 0000000000..726a77cfe5
--- /dev/null
+++ b/test/e2e-go/features/p2p/p2p_basic_test.go
@@ -0,0 +1,63 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package p2p
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+func testP2PWithConfig(t *testing.T, cfgname string) {
+ r := require.New(fixtures.SynchronizedTest(t))
+
+ var fixture fixtures.RestClientFixture
+
+ // Make protocol faster for shorter tests
+ consensus := make(config.ConsensusProtocols)
+ fastProtocol := config.Consensus[protocol.ConsensusCurrentVersion]
+ fastProtocol.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
+ fastProtocol.AgreementFilterTimeoutPeriod0 = 400 * time.Millisecond
+ fastProtocol.AgreementFilterTimeout = 400 * time.Millisecond
+ consensus[protocol.ConsensusCurrentVersion] = fastProtocol
+ fixture.SetConsensus(consensus)
+
+ fixture.Setup(t, filepath.Join("nettemplates", cfgname))
+ defer fixture.ShutdownImpl(true) // preserve logs in testdir
+
+ _, err := fixture.NC.AlgodClient()
+ r.NoError(err)
+
+ err = fixture.WaitForRound(10, 30*time.Second)
+ r.NoError(err)
+}
+
+func TestP2PTwoNodes(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ testP2PWithConfig(t, "TwoNodes50EachP2P.json")
+}
+
+func TestP2PFiveNodes(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ testP2PWithConfig(t, "FiveNodesP2P.json")
+}
diff --git a/test/e2e-go/features/participation/accountParticipationTransitions_test.go b/test/e2e-go/features/participation/accountParticipationTransitions_test.go
index e9a4e5735f..6752af841b 100644
--- a/test/e2e-go/features/participation/accountParticipationTransitions_test.go
+++ b/test/e2e-go/features/participation/accountParticipationTransitions_test.go
@@ -21,6 +21,7 @@ package participation
// deterministic.
import (
+ "errors"
"fmt"
"path/filepath"
"testing"
@@ -32,6 +33,7 @@ import (
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/data/account"
"github.com/algorand/go-algorand/libgoal"
+ "github.com/algorand/go-algorand/libgoal/participation"
"github.com/algorand/go-algorand/test/framework/fixtures"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -39,7 +41,10 @@ import (
// installParticipationKey generates a new key for a given account and installs it with the client.
func installParticipationKey(t *testing.T, client libgoal.Client, addr string, firstValid, lastValid uint64) (resp model.PostParticipationResponse, part account.Participation, err error) {
// Install overlapping participation keys...
- part, filePath, err := client.GenParticipationKeysTo(addr, firstValid, lastValid, 100, t.TempDir())
+ installFunc := func(keyPath string) error {
+ return errors.New("the install directory is provided, so keys should not be installed")
+ }
+ part, filePath, err := participation.GenParticipationKeysTo(addr, firstValid, lastValid, 100, t.TempDir(), installFunc)
require.NoError(t, err)
require.NotNil(t, filePath)
require.Equal(t, addr, part.Parent.String())
diff --git a/test/e2e-go/features/privatenet/privatenet_test.go b/test/e2e-go/features/privatenet/privatenet_test.go
new file mode 100644
index 0000000000..312abed618
--- /dev/null
+++ b/test/e2e-go/features/privatenet/privatenet_test.go
@@ -0,0 +1,62 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+// Check that private networks are started as designed.
+package privatenet
+
+import (
+ "testing"
+
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+// TestPrivateNetworkImportKeys tests that part keys can be exported and
+// imported when starting a private network.
+func TestPrivateNetworkImportKeys(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ // This test takes 5~10 seconds.
+ if testing.Short() {
+ t.Skip()
+ }
+
+ // First test that keys can be exported by using `goal network pregen ...`
+ // Don't start up network, just create genesis files.
+ var goalFixture fixtures.GoalFixture
+ tmpGenDir := t.TempDir()
+ tmpNetDir := t.TempDir()
+ defaultTemplate := "" // Use the default template by omitting the filepath.
+
+ _, err := goalFixture.NetworkPregen(defaultTemplate, tmpGenDir)
+ require.NoError(t, err)
+
+ // Check that if there is an existing directory with same name, test fails.
+ errStr, err := goalFixture.NetworkPregen(defaultTemplate, tmpGenDir)
+ require.Error(t, err)
+ require.Contains(t, errStr, "already exists and is not empty")
+
+ // Then try importing files from same template.
+ err = goalFixture.NetworkCreate(tmpNetDir, "", defaultTemplate, tmpGenDir)
+ require.NoError(t, err)
+
+ err = goalFixture.NetworkStart(tmpNetDir)
+ require.NoError(t, err)
+
+ err = goalFixture.NetworkStop(tmpNetDir)
+ require.NoError(t, err)
+}
diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go
index bba0838c21..ec0d4c2761 100644
--- a/test/e2e-go/features/stateproofs/stateproofs_test.go
+++ b/test/e2e-go/features/stateproofs/stateproofs_test.go
@@ -18,6 +18,7 @@ package stateproofs
import (
"bytes"
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -41,6 +42,7 @@ import (
"github.com/algorand/go-algorand/data/stateproofmsg"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/libgoal"
+ "github.com/algorand/go-algorand/libgoal/participation"
"github.com/algorand/go-algorand/nodecontrol"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/framework/fixtures"
@@ -678,7 +680,10 @@ func installParticipationKey(t *testing.T, client libgoal.Client, addr string, f
defer os.RemoveAll(dir)
// Install overlapping participation keys...
- part, filePath, err := client.GenParticipationKeysTo(addr, firstValid, lastValid, 100, dir)
+ installFunc := func(keyPath string) error {
+ return errors.New("the install directory is provided, so keys should not be installed")
+ }
+ part, filePath, err := participation.GenParticipationKeysTo(addr, firstValid, lastValid, 100, dir, installFunc)
require.NoError(t, err)
require.NotNil(t, filePath)
require.Equal(t, addr, part.Parent.String())
diff --git a/test/e2e-go/features/transactions/onlineStatusChange_test.go b/test/e2e-go/features/transactions/onlineStatusChange_test.go
index a8a9ceb013..7a9e6b3467 100644
--- a/test/e2e-go/features/transactions/onlineStatusChange_test.go
+++ b/test/e2e-go/features/transactions/onlineStatusChange_test.go
@@ -17,6 +17,7 @@
package transactions
import (
+ "errors"
"fmt"
"path/filepath"
"testing"
@@ -24,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/libgoal/participation"
"github.com/algorand/go-algorand/test/framework/fixtures"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -171,7 +173,10 @@ func TestCloseOnError(t *testing.T) {
_, curRound := fixture.GetBalanceAndRound(initiallyOnline)
var partkeyFile string
- _, partkeyFile, err = client.GenParticipationKeysTo(initiallyOffline, 0, curRound+1000, 0, t.TempDir())
+ installFunc := func(keyPath string) error {
+ return errors.New("the install directory is provided, so keys should not be installed")
+ }
+ _, partkeyFile, err = participation.GenParticipationKeysTo(initiallyOffline, 0, curRound+1000, 0, t.TempDir(), installFunc)
a.NoError(err)
// make a participation key for initiallyOffline
diff --git a/test/e2e-go/restAPI/helpers.go b/test/e2e-go/restAPI/helpers.go
new file mode 100644
index 0000000000..3e85020e94
--- /dev/null
+++ b/test/e2e-go/restAPI/helpers.go
@@ -0,0 +1,143 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package restapi
+
+import (
+ "errors"
+ "math/rand"
+ "testing"
+ "time"
+
+ v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2"
+ "github.com/algorand/go-algorand/libgoal"
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/stretchr/testify/require"
+)
+
+// helper generates a random Uppercase Alphabetic ASCII char
+func randomUpperAlphaAsByte() byte {
+ return byte(65 + rand.Intn(25))
+}
+
+// RandomString helper generates a random string
+// snippet credit to many places, one such place is https://medium.com/@kpbird/golang-generate-fixed-size-random-string-dd6dbd5e63c0
+func RandomString(len int) string {
+ // re-seed the RNG to mitigate randomString collisions across tests
+ rand.Seed(time.Now().UnixNano())
+ bytes := make([]byte, len)
+ for i := 0; i < len; i++ {
+ bytes[i] = randomUpperAlphaAsByte()
+ }
+ return string(bytes)
+}
+
+// helper replaces a string's character at index
+func replaceAtIndex(in string, r rune, i int) string {
+ out := []rune(in)
+ out[i] = r
+ return string(out)
+}
+
+// helper replaces a string's character at index with a random, different uppercase alphabetic ascii char
+func mutateStringAtIndex(in string, i int) (out string) {
+ out = in
+ for out == in {
+ out = replaceAtIndex(in, rune(randomUpperAlphaAsByte()), i)
+ }
+ return out
+}
+
+// GetMaxBalAddr returns the address with the highest balance
+func GetMaxBalAddr(t *testing.T, testClient libgoal.Client, addresses []string) (someBal uint64, someAddress string) {
+ a := require.New(fixtures.SynchronizedTest(t))
+ someBal = 0
+ for _, addr := range addresses {
+ bal, err := testClient.GetBalance(addr)
+ a.NoError(err)
+ if bal > someBal {
+ someAddress = addr
+ someBal = bal
+ }
+ }
+ return
+}
+
+// GetDestAddr returns an address that is not someAddress
+func GetDestAddr(t *testing.T, testClient libgoal.Client, addresses []string, someAddress string, wh []byte) (toAddress string) {
+ a := require.New(fixtures.SynchronizedTest(t))
+ if len(addresses) > 1 {
+ for _, addr := range addresses {
+ if addr != someAddress {
+ toAddress = addr
+ return
+ }
+ }
+ }
+ var err error
+ toAddress, err = testClient.GenerateAddress(wh)
+ a.NoError(err)
+ return
+}
+
+// WaitForRoundOne waits for round 1
+func WaitForRoundOne(t *testing.T, testClient libgoal.Client) {
+ a := require.New(fixtures.SynchronizedTest(t))
+ errchan := make(chan error)
+ quit := make(chan struct{})
+ go func() {
+ _, xe := testClient.WaitForRound(1)
+ select {
+ case errchan <- xe:
+ case <-quit:
+ }
+ }()
+ select {
+ case err := <-errchan:
+ a.NoError(err)
+ case <-time.After(1 * time.Minute): // Wait 1 minute (same as WaitForRound)
+ close(quit)
+ t.Fatalf("%s: timeout waiting for round 1", t.Name())
+ }
+}
+
+var errWaitForTransactionTimeout = errors.New("wait for transaction timed out")
+
+// WaitForTransaction waits for a transaction to be confirmed
+func WaitForTransaction(t *testing.T, testClient libgoal.Client, txID string, timeout time.Duration) (tx v2.PreEncodedTxInfo, err error) {
+ a := require.New(fixtures.SynchronizedTest(t))
+ rnd, err := testClient.Status()
+ a.NoError(err)
+ if rnd.LastRound == 0 {
+ t.Fatal("it is currently round 0 but we need to wait for a transaction that might happen this round but we'll never know if that happens because ConfirmedRound==0 is indestinguishable from not having happened")
+ }
+ timeoutTime := time.Now().Add(timeout)
+ for {
+ tx, err = testClient.ParsedPendingTransaction(txID)
+ if err == nil {
+ a.NotEmpty(tx)
+ a.Empty(tx.PoolError)
+ if tx.ConfirmedRound != nil && *tx.ConfirmedRound > 0 {
+ return
+ }
+ }
+ if time.Now().After(timeoutTime) {
+ err = errWaitForTransactionTimeout
+ return
+ }
+ time.Sleep(time.Second)
+ }
+}
diff --git a/test/e2e-go/restAPI/other/appsRestAPI_test.go b/test/e2e-go/restAPI/other/appsRestAPI_test.go
new file mode 100644
index 0000000000..8abe6a4dbc
--- /dev/null
+++ b/test/e2e-go/restAPI/other/appsRestAPI_test.go
@@ -0,0 +1,555 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package other
+
+import (
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "path/filepath"
+ "sort"
+ "testing"
+ "time"
+
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/daemon/algod/api/client"
+ "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+
+ helper "github.com/algorand/go-algorand/test/e2e-go/restAPI"
+)
+
+func TestPendingTransactionInfoInnerTxnAssetCreate(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ testClient.WaitForRound(1)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, someAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if someAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ prog := `#pragma version 5
+txn ApplicationID
+bz end
+itxn_begin
+int acfg
+itxn_field TypeEnum
+int 1000000
+itxn_field ConfigAssetTotal
+int 3
+itxn_field ConfigAssetDecimals
+byte "oz"
+itxn_field ConfigAssetUnitName
+byte "Gold"
+itxn_field ConfigAssetName
+byte "https://gold.rush/"
+itxn_field ConfigAssetURL
+byte 0x67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b
+itxn_field ConfigAssetMetadataHash
+itxn_submit
+end:
+int 1
+return
+`
+ ops, err := logic.AssembleString(prog)
+ a.NoError(err)
+ approv := ops.Program
+ ops, err = logic.AssembleString("#pragma version 5 \nint 1")
+ clst := ops.Program
+ a.NoError(err)
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ // create app
+ appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0)
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+
+ // get app ID
+ submittedAppCreateTxn, err := testClient.PendingTransactionInformation(appCreateTxID)
+ a.NoError(err)
+ a.NotNil(submittedAppCreateTxn.ApplicationIndex)
+ createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+ a.NotZero(createdAppID)
+
+ // fund app account
+ appFundTxn, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, createdAppID.Address().String(), 0, 1_000_000, nil, "", 0, 0)
+ a.NoError(err)
+ appFundTxID := appFundTxn.ID()
+ _, err = helper.WaitForTransaction(t, testClient, appFundTxID.String(), 30*time.Second)
+ a.NoError(err)
+
+ // call app, which will issue an ASA create inner txn
+ appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(uint64(createdAppID), nil, nil, nil, nil, nil)
+ a.NoError(err)
+ appCallTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn)
+ a.NoError(err)
+ appCallTxnTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCallTxn)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, appCallTxnTxID, 30*time.Second)
+ a.NoError(err)
+
+ // verify pending txn info of outer txn
+ submittedAppCallTxn, err := testClient.PendingTransactionInformation(appCallTxnTxID)
+ a.NoError(err)
+ a.Nil(submittedAppCallTxn.ApplicationIndex)
+ a.Nil(submittedAppCallTxn.AssetIndex)
+ a.NotNil(submittedAppCallTxn.InnerTxns)
+ a.Len(*submittedAppCallTxn.InnerTxns, 1)
+
+ // verify pending txn info of inner txn
+ innerTxn := (*submittedAppCallTxn.InnerTxns)[0]
+ a.Nil(innerTxn.ApplicationIndex)
+ a.NotNil(innerTxn.AssetIndex)
+ createdAssetID := *innerTxn.AssetIndex
+ a.NotZero(createdAssetID)
+
+ createdAssetInfo, err := testClient.AssetInformation(createdAssetID)
+ a.NoError(err)
+ a.Equal(createdAssetID, createdAssetInfo.Index)
+ a.Equal(createdAppID.Address().String(), createdAssetInfo.Params.Creator)
+ a.Equal(uint64(1000000), createdAssetInfo.Params.Total)
+ a.Equal(uint64(3), createdAssetInfo.Params.Decimals)
+ a.Equal("oz", *createdAssetInfo.Params.UnitName)
+ a.Equal("Gold", *createdAssetInfo.Params.Name)
+ a.Equal("https://gold.rush/", *createdAssetInfo.Params.Url)
+ expectedMetadata, err := hex.DecodeString("67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b")
+ a.NoError(err)
+ a.Equal(expectedMetadata, *createdAssetInfo.Params.MetadataHash)
+}
+
+func TestBoxNamesByAppID(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ testClient.WaitForRound(1)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, someAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if someAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ prog := `#pragma version 8
+ txn ApplicationID
+ bz end // create the app
+ txn NumAppArgs
+ bz end // approve when no app args
+ txn ApplicationArgs 0 // [arg[0]] // fails if no args && app already exists
+ byte "create" // [arg[0], "create"] // create box named arg[1]
+ == // [arg[0]=?="create"]
+ bz del // "create" ? continue : goto del
+ int 5 // [5]
+ txn ApplicationArgs 1 // [5, arg[1]]
+ swap
+ box_create // [] // boxes: arg[1] -> [5]byte
+ assert
+ b end
+del: // delete box arg[1]
+ txn ApplicationArgs 0 // [arg[0]]
+ byte "delete" // [arg[0], "delete"]
+ == // [arg[0]=?="delete"]
+ bz set // "delete" ? continue : goto set
+ txn ApplicationArgs 1 // [arg[1]]
+ box_del // del boxes[arg[1]]
+ assert
+ b end
+set: // put arg[1] at start of box arg[0] ... so actually a _partial_ "set"
+ txn ApplicationArgs 0 // [arg[0]]
+ byte "set" // [arg[0], "set"]
+ == // [arg[0]=?="set"]
+ bz bad // "delete" ? continue : goto bad
+ txn ApplicationArgs 1 // [arg[1]]
+ int 0 // [arg[1], 0]
+ txn ApplicationArgs 2 // [arg[1], 0, arg[2]]
+ box_replace // [] // boxes: arg[1] -> replace(boxes[arg[1]], 0, arg[2])
+ b end
+bad:
+ err
+end:
+ int 1
+`
+ ops, err := logic.AssembleString(prog)
+ a.NoError(err)
+ approval := ops.Program
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ // create app
+ appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
+ 0, nil, nil, nil,
+ nil, nil, transactions.NoOpOC,
+ approval, clearState, gl, lc, 0,
+ )
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+
+ // get app ID
+ submittedAppCreateTxn, err := testClient.PendingTransactionInformation(appCreateTxID)
+ a.NoError(err)
+ a.NotNil(submittedAppCreateTxn.ApplicationIndex)
+ createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+ a.NotZero(createdAppID)
+
+ // fund app account
+ appFundTxn, err := testClient.SendPaymentFromWallet(
+ wh, nil, someAddress, createdAppID.Address().String(),
+ 0, 10_000_000, nil, "", 0, 0,
+ )
+ a.NoError(err)
+ appFundTxID := appFundTxn.ID()
+ _, err = helper.WaitForTransaction(t, testClient, appFundTxID.String(), 30*time.Second)
+ a.NoError(err)
+
+ createdBoxName := map[string]bool{}
+ var createdBoxCount uint64 = 0
+
+ // define operate box helper
+ operateBoxAndSendTxn := func(operation string, boxNames []string, boxValues []string, errPrefix ...string) {
+ txns := make([]transactions.Transaction, len(boxNames))
+ txIDs := make(map[string]string, len(boxNames))
+
+ for i := 0; i < len(boxNames); i++ {
+ appArgs := [][]byte{
+ []byte(operation),
+ []byte(boxNames[i]),
+ []byte(boxValues[i]),
+ }
+ boxRef := transactions.BoxRef{
+ Name: []byte(boxNames[i]),
+ Index: 0,
+ }
+
+ txns[i], err = testClient.MakeUnsignedAppNoOpTx(
+ uint64(createdAppID), appArgs,
+ nil, nil, nil,
+ []transactions.BoxRef{boxRef},
+ )
+ a.NoError(err)
+ txns[i], err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, txns[i])
+ a.NoError(err)
+ txIDs[txns[i].ID().String()] = someAddress
+ }
+
+ var gid crypto.Digest
+ gid, err = testClient.GroupID(txns)
+ a.NoError(err)
+
+ stxns := make([]transactions.SignedTxn, len(boxNames))
+ for i := 0; i < len(boxNames); i++ {
+ txns[i].Group = gid
+ wh, err = testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ stxns[i], err = testClient.SignTransactionWithWallet(wh, nil, txns[i])
+ a.NoError(err)
+ }
+
+ err = testClient.BroadcastTransactionGroup(stxns)
+ if len(errPrefix) == 0 {
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, txns[0].ID().String(), 30*time.Second)
+ a.NoError(err)
+ } else {
+ a.ErrorContains(err, errPrefix[0])
+ }
+ }
+
+ // `assertErrorResponse` confirms the _Result limit exceeded_ error response provides expected fields and values.
+ assertErrorResponse := func(err error, expectedCount, requestedMax uint64) {
+ a.Error(err)
+ e := err.(client.HTTPError)
+ a.Equal(400, e.StatusCode)
+
+ var er *model.ErrorResponse
+ err = protocol.DecodeJSON([]byte(e.ErrorString), &er)
+ a.NoError(err)
+ a.Equal("Result limit exceeded", er.Message)
+ a.Equal(uint64(100000), ((*er.Data)["max-api-box-per-application"]).(uint64))
+ a.Equal(requestedMax, ((*er.Data)["max"]).(uint64))
+ a.Equal(expectedCount, ((*er.Data)["total-boxes"]).(uint64))
+
+ a.Len(*er.Data, 3, fmt.Sprintf("error response (%v) contains unverified fields. Extend test for new fields.", *er.Data))
+ }
+
+ // `assertBoxCount` sanity checks that the REST API respects `expectedCount` through different queries against app ID = `createdAppID`.
+ assertBoxCount := func(expectedCount uint64) {
+ // Query without client-side limit.
+ resp, err := testClient.ApplicationBoxes(uint64(createdAppID), 0)
+ a.NoError(err)
+ a.Len(resp.Boxes, int(expectedCount))
+
+ // Query with requested max < expected expectedCount.
+ _, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount-1)
+ assertErrorResponse(err, expectedCount, expectedCount-1)
+
+ // Query with requested max == expected expectedCount.
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount)
+ a.NoError(err)
+ a.Len(resp.Boxes, int(expectedCount))
+
+ // Query with requested max > expected expectedCount.
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount+1)
+ a.NoError(err)
+ a.Len(resp.Boxes, int(expectedCount))
+ }
+
+ // helper function, take operation and a slice of box names
+ // then submit transaction group containing all operations on box names
+ // Then we check these boxes are appropriately created/deleted
+ operateAndMatchRes := func(operation string, boxNames []string) {
+ boxValues := make([]string, len(boxNames))
+ if operation == "create" {
+ for i, box := range boxNames {
+ keyValid, ok := createdBoxName[box]
+ a.False(ok && keyValid)
+ boxValues[i] = ""
+ }
+ } else if operation == "delete" {
+ for i, box := range boxNames {
+ keyValid, ok := createdBoxName[box]
+ a.True(keyValid == ok)
+ boxValues[i] = ""
+ }
+ } else {
+ a.Failf("Unknown operation %s", operation)
+ }
+
+ operateBoxAndSendTxn(operation, boxNames, boxValues)
+
+ if operation == "create" {
+ for _, box := range boxNames {
+ createdBoxName[box] = true
+ }
+ createdBoxCount += uint64(len(boxNames))
+ } else if operation == "delete" {
+ for _, box := range boxNames {
+ createdBoxName[box] = false
+ }
+ createdBoxCount -= uint64(len(boxNames))
+ }
+
+ var resp model.BoxesResponse
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), 0)
+ a.NoError(err)
+
+ expectedCreatedBoxes := make([]string, 0, createdBoxCount)
+ for name, isCreate := range createdBoxName {
+ if isCreate {
+ expectedCreatedBoxes = append(expectedCreatedBoxes, name)
+ }
+ }
+ sort.Strings(expectedCreatedBoxes)
+
+ actualBoxes := make([]string, len(resp.Boxes))
+ for i, box := range resp.Boxes {
+ actualBoxes[i] = string(box.Name)
+ }
+ sort.Strings(actualBoxes)
+
+ a.Equal(expectedCreatedBoxes, actualBoxes)
+ }
+
+ testingBoxNames := []string{
+ ` `,
+ ` `,
+ ` ? = % ;`,
+ `; DROP *;`,
+ `OR 1 = 1;`,
+ `" ; SELECT * FROM kvstore; DROP acctrounds; `,
+ `背负青天而莫之夭阏者,而后乃今将图南。`,
+ `於浩歌狂熱之際中寒﹔於天上看見深淵。`,
+ `於一切眼中看見無所有﹔於無所希望中得救。`,
+ `有一遊魂,化為長蛇,口有毒牙。`,
+ `不以嚙人,自嚙其身,終以殞顛。`,
+ `那些智力超常的人啊`,
+ `认为已经,熟悉了云和闪电的脾气`,
+ `就不再迷惑,就不必了解自己,世界和他人`,
+ `每天只管,被微风吹拂,与猛虎谈情`,
+ `他们从来,不需要楼梯,只有窗口`,
+ `把一切交付于梦境,和优美的浪潮`,
+ `在这颗行星所有的酒馆,青春自由似乎理所应得`,
+ `面向涣散的未来,只唱情歌,看不到坦克`,
+ `在科学和啤酒都不能安抚的夜晚`,
+ `他们丢失了四季,惶惑之行开始`,
+ `这颗行星所有的酒馆,无法听到远方的呼喊`,
+ `野心勃勃的灯火,瞬间吞没黑暗的脸庞`,
+ `b64:APj/AA==`,
+ `str:123.3/aa\\0`,
+ string([]byte{0, 255, 254, 254}),
+ string([]byte{0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}),
+ `; SELECT key from kvstore WHERE key LIKE %;`,
+ `?&%!=`,
+ "SELECT * FROM kvstore " + string([]byte{0, 0}) + " WHERE key LIKE %; ",
+ string([]byte{'%', 'a', 'b', 'c', 0, 0, '%', 'a', '!'}),
+ `
+`,
+ `™£´´∂ƒ∂ƒßƒ©∑®ƒß∂†¬∆`,
+ `∑´´˙©˚¬∆ßåƒ√¬`,
+ }
+
+ // Happy Vanilla paths:
+ resp, err := testClient.ApplicationBoxes(uint64(createdAppID), 0)
+ a.NoError(err)
+ a.Empty(resp.Boxes)
+
+ // Some Un-Happy / Non-Vanilla paths:
+
+ // Even though the next box _does not exist_ as asserted by the error below,
+ // querying it for boxes _DOES NOT ERROR_. There is no easy way to tell
+ // the difference between non-existing boxes for an app that once existed
+ // vs. an app the NEVER existed.
+ nonexistantAppIndex := uint64(1337)
+ _, err = testClient.ApplicationInformation(nonexistantAppIndex)
+ a.ErrorContains(err, "application does not exist")
+ resp, err = testClient.ApplicationBoxes(nonexistantAppIndex, 0)
+ a.NoError(err)
+ a.Len(resp.Boxes, 0)
+
+ operateBoxAndSendTxn("create", []string{``}, []string{``}, "box names may not be zero length")
+
+ for i := 0; i < len(testingBoxNames); i += 16 {
+ var strSliceTest []string
+ // grouping box names to operate, and create such boxes
+ if i+16 >= len(testingBoxNames) {
+ strSliceTest = testingBoxNames[i:]
+ } else {
+ strSliceTest = testingBoxNames[i : i+16]
+ }
+ operateAndMatchRes("create", strSliceTest)
+ }
+
+ assertBoxCount(uint64(len(testingBoxNames)))
+
+ for i := 0; i < len(testingBoxNames); i += 16 {
+ var strSliceTest []string
+ // grouping box names to operate, and delete such boxes
+ if i+16 >= len(testingBoxNames) {
+ strSliceTest = testingBoxNames[i:]
+ } else {
+ strSliceTest = testingBoxNames[i : i+16]
+ }
+ operateAndMatchRes("delete", strSliceTest)
+ }
+
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), 0)
+ a.NoError(err)
+ a.Empty(resp.Boxes)
+
+ // Get Box value from box name
+ encodeInt := func(n uint64) []byte {
+ ibytes := make([]byte, 8)
+ binary.BigEndian.PutUint64(ibytes, n)
+ return ibytes
+ }
+
+ boxTests := []struct {
+ name []byte
+ encodedName string
+ value []byte
+ }{
+ {[]byte("foo"), "str:foo", []byte("bar12")},
+ {encodeInt(12321), "int:12321", []byte{0, 1, 254, 3, 2}},
+ {[]byte{0, 248, 255, 32}, "b64:APj/IA==", []byte("lux56")},
+ }
+
+ for _, boxTest := range boxTests {
+ // Box values are 5 bytes, as defined by the test TEAL program.
+ operateBoxAndSendTxn("create", []string{string(boxTest.name)}, []string{""})
+ operateBoxAndSendTxn("set", []string{string(boxTest.name)}, []string{string(boxTest.value)})
+
+ currentRoundBeforeBoxes, err := testClient.CurrentRound()
+ a.NoError(err)
+ boxResponse, err := testClient.GetApplicationBoxByName(uint64(createdAppID), boxTest.encodedName)
+ a.NoError(err)
+ currentRoundAfterBoxes, err := testClient.CurrentRound()
+ a.NoError(err)
+ a.Equal(boxTest.name, boxResponse.Name)
+ a.Equal(boxTest.value, boxResponse.Value)
+ // To reduce flakiness, only check the round from boxes is within a range.
+ a.GreaterOrEqual(boxResponse.Round, currentRoundBeforeBoxes)
+ a.LessOrEqual(boxResponse.Round, currentRoundAfterBoxes)
+ }
+
+ const numberOfBoxesRemaining = uint64(3)
+ assertBoxCount(numberOfBoxesRemaining)
+
+ // Non-vanilla. Wasteful but correct. Can delete an app without first cleaning up its boxes.
+ appAccountData, err := testClient.AccountData(createdAppID.Address().String())
+ a.NoError(err)
+ a.Equal(numberOfBoxesRemaining, appAccountData.TotalBoxes)
+ a.Equal(uint64(30), appAccountData.TotalBoxBytes)
+
+ // delete the app
+ appDeleteTxn, err := testClient.MakeUnsignedAppDeleteTx(uint64(createdAppID), nil, nil, nil, nil, nil)
+ a.NoError(err)
+ appDeleteTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appDeleteTxn)
+ a.NoError(err)
+ appDeleteTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appDeleteTxn)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, appDeleteTxID, 30*time.Second)
+ a.NoError(err)
+
+ _, err = testClient.ApplicationInformation(uint64(createdAppID))
+ a.ErrorContains(err, "application does not exist")
+
+ assertBoxCount(numberOfBoxesRemaining)
+}
diff --git a/test/e2e-go/restAPI/other/misc_test.go b/test/e2e-go/restAPI/other/misc_test.go
new file mode 100644
index 0000000000..b77a844527
--- /dev/null
+++ b/test/e2e-go/restAPI/other/misc_test.go
@@ -0,0 +1,241 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package other
+
+import (
+ "os"
+ "path"
+ "path/filepath"
+ "testing"
+
+ "github.com/algorand/go-algorand/daemon/algod/api/client"
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/algorand/go-algorand/util/tokens"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestDisabledAPIConfig(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "DisableAPIAuth.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ statusResponse, err := testClient.Status()
+ a.NoError(err)
+ a.NotEmpty(statusResponse)
+ statusResponse2, err := testClient.Status()
+ a.NoError(err)
+ a.NotEmpty(statusResponse2)
+ a.True(statusResponse2.LastRound >= statusResponse.LastRound)
+
+ // Check the public token isn't created when the API authentication is disabled
+ nc, err := localFixture.GetNodeController("Primary")
+ assert.NoError(t, err)
+ _, err = os.Stat(path.Join(nc.GetDataDir(), tokens.AlgodAdminTokenFilename))
+ assert.NoError(t, err)
+ _, err = os.Stat(path.Join(nc.GetDataDir(), tokens.AlgodTokenFilename))
+ assert.True(t, os.IsNotExist(err))
+
+ // check public api works without a token
+ testClient.WaitForRound(1)
+ _, err = testClient.Block(1)
+ assert.NoError(t, err)
+ // check admin api works with the generated token
+ _, err = testClient.GetParticipationKeys()
+ assert.NoError(t, err)
+ // check admin api doesn't work with an invalid token
+ algodURL, err := nc.ServerURL()
+ assert.NoError(t, err)
+ client := client.MakeRestClient(algodURL, "")
+ _, err = client.GetParticipationKeys()
+ assert.Contains(t, err.Error(), "Invalid API Token")
+}
+
+func TestSendingNotClosingAccountFails(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ // use a local fixture because we might really mess with the balances
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
+ defer localFixture.Shutdown()
+ testClient := localFixture.LibGoalClient
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ var emptyAddress string
+ for _, addr := range addresses {
+ bal, err := testClient.GetBalance(addr)
+ a.NoError(err)
+ if bal == 0 {
+ emptyAddress = addr
+ break
+ }
+ }
+ if emptyAddress == "" {
+ emptyAddress, err = testClient.GenerateAddress(wh)
+ a.NoError(err)
+ }
+ var someAddress string
+ someBal := uint64(0)
+ for _, addr := range addresses {
+ if addr != emptyAddress {
+ bal, err := testClient.GetBalance(addr)
+ a.NoError(err)
+ if bal > someBal {
+ someAddress = addr
+ someBal = bal
+ }
+ }
+ }
+ if someAddress == "" {
+ t.Error("no addr with funds")
+ }
+ amt := someBal - 10000 - 1
+ _, err = testClient.SendPaymentFromWallet(wh, nil, someAddress, emptyAddress, 10000, amt, nil, "", 0, 0)
+ a.Error(err)
+}
+
+func TestClientCanGetPendingTransactions(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+ wh, _ := testClient.GetUnencryptedWalletHandle()
+ addresses, _ := testClient.ListAddresses(wh)
+ fromAddress := addresses[0]
+ toAddress, _ := testClient.GenerateAddress(wh)
+ // We may not need to kill the other node, but do it anyways to ensure the txn never gets committed
+ nc, _ := localFixture.GetNodeController("Node")
+ err := nc.FullStop()
+ a.NoError(err)
+
+ minTxnFee, minAcctBalance, err := localFixture.CurrentMinFeeAndBalance()
+ a.NoError(err)
+
+ // Check that a single pending txn is corectly displayed
+ tx, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress, minTxnFee, minAcctBalance, nil)
+ a.NoError(err)
+ statusResponse, err := testClient.GetParsedPendingTransactions(0)
+ a.NoError(err)
+ a.NotEmpty(statusResponse)
+ a.True(statusResponse.TotalTransactions == 1)
+ a.True(len(statusResponse.TopTransactions) == 1)
+
+ // Parse response into SignedTxn
+ pendingTxn := statusResponse.TopTransactions[0]
+ a.True(pendingTxn.Txn.ID().String() == tx.ID().String())
+}
+
+func TestClientTruncatesPendingTransactions(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+ wh, _ := testClient.GetUnencryptedWalletHandle()
+ nc, _ := localFixture.GetNodeController("Node")
+ err := nc.FullStop()
+ a.NoError(err)
+
+ minTxnFee, minAcctBalance, err := localFixture.CurrentMinFeeAndBalance()
+ a.NoError(err)
+
+ NumTxns := 10
+ MaxTxns := 7
+ addresses, _ := testClient.ListAddresses(wh)
+ fromAddress := addresses[0]
+ txIDsSeen := make(map[string]bool)
+ for i := 0; i < NumTxns; i++ {
+ toAddress, _ := testClient.GenerateAddress(wh)
+ tx2, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress, minTxnFee, minAcctBalance, nil)
+ a.NoError(err)
+ txIDsSeen[tx2.ID().String()] = true
+ }
+ statusResponse, err := testClient.GetParsedPendingTransactions(uint64(MaxTxns))
+ a.NoError(err)
+ a.True(int(statusResponse.TotalTransactions) == NumTxns)
+ a.True(len(statusResponse.TopTransactions) == MaxTxns)
+ for _, tx := range statusResponse.TopTransactions {
+ a.True(txIDsSeen[tx.Txn.ID().String()])
+ delete(txIDsSeen, tx.Txn.ID().String())
+ }
+ a.True(len(txIDsSeen) == NumTxns-MaxTxns)
+}
+
+func TestClientPrioritizesPendingTransactions(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ t.Skip("new FIFO pool does not have prioritization")
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+ wh, _ := testClient.GetUnencryptedWalletHandle()
+ addresses, _ := testClient.ListAddresses(wh)
+ fromAddress := addresses[0]
+ toAddress, _ := testClient.GenerateAddress(wh)
+ nc, _ := localFixture.GetNodeController("Node")
+ err := nc.FullStop()
+ a.NoError(err)
+
+ minTxnFee, minAcctBalance, err := localFixture.CurrentMinFeeAndBalance()
+ a.NoError(err)
+
+ NumTxns := 5
+ MaxTxns := 3
+ for i := 0; i < NumTxns; i++ {
+ toAddress2, _ := testClient.GenerateAddress(wh)
+ _, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress2, minTxnFee, minAcctBalance, nil)
+ a.NoError(err)
+ }
+
+ // Add a very high fee transaction. This should have first priority
+ // (even if we don't know the encoding length of the underlying signed txn)
+ txHigh, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress, minTxnFee*10, minAcctBalance, nil)
+ a.NoError(err)
+
+ statusResponse, err := testClient.GetParsedPendingTransactions(uint64(MaxTxns))
+ a.NoError(err)
+ a.NotEmpty(statusResponse)
+ a.True(int(statusResponse.TotalTransactions) == NumTxns+1)
+ a.True(len(statusResponse.TopTransactions) == MaxTxns)
+
+ pendingTxn := statusResponse.TopTransactions[0]
+ a.True(pendingTxn.Txn.ID().String() == txHigh.ID().String())
+}
diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go
index fc21250269..65292563da 100644
--- a/test/e2e-go/restAPI/restClient_test.go
+++ b/test/e2e-go/restAPI/restClient_test.go
@@ -18,17 +18,10 @@ package restapi
import (
"context"
- "encoding/binary"
- "encoding/hex"
- "errors"
"flag"
- "fmt"
"math"
- "math/rand"
- "net/http"
"os"
"path/filepath"
- "sort"
"strings"
"testing"
"time"
@@ -38,21 +31,16 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
- "github.com/algorand/go-algorand/daemon/algod/api/client"
- v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2"
- "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
- "github.com/algorand/go-algorand/data/account"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
- "github.com/algorand/go-algorand/data/transactions/logic"
- "github.com/algorand/go-algorand/ledger/simulation"
- "github.com/algorand/go-algorand/libgoal"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/framework/fixtures"
"github.com/algorand/go-algorand/test/partitiontest"
- "github.com/algorand/go-algorand/util/db"
)
+// NOTE: Tests in this file use a shared a network.
+// TestMain runs all tests in this package using that shared network.
+
var fixture fixtures.RestClientFixture
func TestMain(m *testing.M) {
@@ -71,116 +59,6 @@ func TestMain(m *testing.M) {
}
}
-// helper generates a random Uppercase Alphabetic ASCII char
-func randomUpperAlphaAsByte() byte {
- return byte(65 + rand.Intn(25))
-}
-
-// helper generates a random string
-// snippet credit to many places, one such place is https://medium.com/@kpbird/golang-generate-fixed-size-random-string-dd6dbd5e63c0
-func randomString(len int) string {
- // re-seed the RNG to mitigate randomString collisions across tests
- rand.Seed(time.Now().UnixNano())
- bytes := make([]byte, len)
- for i := 0; i < len; i++ {
- bytes[i] = randomUpperAlphaAsByte()
- }
- return string(bytes)
-}
-
-// helper replaces a string's character at index
-func replaceAtIndex(in string, r rune, i int) string {
- out := []rune(in)
- out[i] = r
- return string(out)
-}
-
-// helper replaces a string's character at index with a random, different uppercase alphabetic ascii char
-func mutateStringAtIndex(in string, i int) (out string) {
- out = in
- for out == in {
- out = replaceAtIndex(in, rune(randomUpperAlphaAsByte()), i)
- }
- return out
-}
-
-func getMaxBalAddr(t *testing.T, testClient libgoal.Client, addresses []string) (someBal uint64, someAddress string) {
- a := require.New(fixtures.SynchronizedTest(t))
- someBal = 0
- for _, addr := range addresses {
- bal, err := testClient.GetBalance(addr)
- a.NoError(err)
- if bal > someBal {
- someAddress = addr
- someBal = bal
- }
- }
- return
-}
-
-func getDestAddr(t *testing.T, testClient libgoal.Client, addresses []string, someAddress string, wh []byte) (toAddress string) {
- a := require.New(fixtures.SynchronizedTest(t))
- if len(addresses) > 1 {
- for _, addr := range addresses {
- if addr != someAddress {
- toAddress = addr
- return
- }
- }
- }
- var err error
- toAddress, err = testClient.GenerateAddress(wh)
- a.NoError(err)
- return
-}
-
-func waitForRoundOne(t *testing.T, testClient libgoal.Client) {
- a := require.New(fixtures.SynchronizedTest(t))
- errchan := make(chan error)
- quit := make(chan struct{})
- go func() {
- _, xe := testClient.WaitForRound(1)
- select {
- case errchan <- xe:
- case <-quit:
- }
- }()
- select {
- case err := <-errchan:
- a.NoError(err)
- case <-time.After(1 * time.Minute): // Wait 1 minute (same as WaitForRound)
- close(quit)
- t.Fatalf("%s: timeout waiting for round 1", t.Name())
- }
-}
-
-var errWaitForTransactionTimeout = errors.New("wait for transaction timed out")
-
-func waitForTransaction(t *testing.T, testClient libgoal.Client, fromAddress, txID string, timeout time.Duration) (tx v2.PreEncodedTxInfo, err error) {
- a := require.New(fixtures.SynchronizedTest(t))
- rnd, err := testClient.Status()
- a.NoError(err)
- if rnd.LastRound == 0 {
- t.Fatal("it is currently round 0 but we need to wait for a transaction that might happen this round but we'll never know if that happens because ConfirmedRound==0 is indestinguishable from not having happened")
- }
- timeoutTime := time.Now().Add(timeout)
- for {
- tx, err = testClient.ParsedPendingTransaction(txID)
- if err == nil {
- a.NotEmpty(tx)
- a.Empty(tx.PoolError)
- if tx.ConfirmedRound != nil && *tx.ConfirmedRound > 0 {
- return
- }
- }
- if time.Now().After(timeoutTime) {
- err = errWaitForTransactionTimeout
- return
- }
- time.Sleep(time.Second)
- }
-}
-
func TestClientCanGetStatus(t *testing.T) {
partitiontest.PartitionTest(t)
defer fixtures.ShutdownSynchronizedTest(t)
@@ -255,7 +133,7 @@ func TestClientCanGetBlockInfo(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
blockResponse, err := testClient.Block(1)
a.NoError(err)
a.NotEmpty(blockResponse)
@@ -367,7 +245,7 @@ func TestClientOversizedNote(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
wh, err := testClient.GetUnencryptedWalletHandle()
a.NoError(err)
addresses, err := testClient.ListAddresses(wh)
@@ -393,21 +271,21 @@ func TestClientCanSendAndGetNote(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
wh, err := testClient.GetUnencryptedWalletHandle()
a.NoError(err)
addresses, err := testClient.ListAddresses(wh)
a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
+ _, someAddress := GetMaxBalAddr(t, testClient, addresses)
if someAddress == "" {
t.Error("no addr with funds")
}
- toAddress := getDestAddr(t, testClient, addresses, someAddress, wh)
+ toAddress := GetDestAddr(t, testClient, addresses, someAddress, wh)
maxTxnNoteBytes := config.Consensus[protocol.ConsensusCurrentVersion].MaxTxnNoteBytes
note := make([]byte, maxTxnNoteBytes)
tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, note, "", 0, 0)
a.NoError(err)
- txStatus, err := waitForTransaction(t, testClient, someAddress, tx.ID().String(), 30*time.Second)
+ txStatus, err := WaitForTransaction(t, testClient, tx.ID().String(), 30*time.Second)
a.NoError(err)
a.Equal(note, txStatus.Txn.Txn.Note)
}
@@ -419,21 +297,21 @@ func TestClientCanGetTransactionStatus(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
wh, err := testClient.GetUnencryptedWalletHandle()
a.NoError(err)
addresses, err := testClient.ListAddresses(wh)
a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
+ _, someAddress := GetMaxBalAddr(t, testClient, addresses)
if someAddress == "" {
t.Error("no addr with funds")
}
- toAddress := getDestAddr(t, testClient, addresses, someAddress, wh)
+ toAddress := GetDestAddr(t, testClient, addresses, someAddress, wh)
tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, nil, "", 0, 0)
t.Log(string(protocol.EncodeJSON(tx)))
a.NoError(err)
t.Log(tx.ID().String())
- _, err = waitForTransaction(t, testClient, someAddress, tx.ID().String(), 30*time.Second)
+ _, err = WaitForTransaction(t, testClient, tx.ID().String(), 30*time.Second)
a.NoError(err)
}
@@ -444,12 +322,12 @@ func TestAccountBalance(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
wh, err := testClient.GetUnencryptedWalletHandle()
a.NoError(err)
addresses, err := testClient.ListAddresses(wh)
a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
+ _, someAddress := GetMaxBalAddr(t, testClient, addresses)
if someAddress == "" {
t.Error("no addr with funds")
}
@@ -458,7 +336,7 @@ func TestAccountBalance(t *testing.T) {
a.NoError(err)
tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, nil, "", 0, 0)
a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, tx.ID().String(), 30*time.Second)
+ _, err = WaitForTransaction(t, testClient, tx.ID().String(), 30*time.Second)
a.NoError(err)
account, err := testClient.AccountInformation(toAddress, false)
@@ -474,12 +352,12 @@ func TestAccountParticipationInfo(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
wh, err := testClient.GetUnencryptedWalletHandle()
a.NoError(err)
addresses, err := testClient.ListAddresses(wh)
a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
+ _, someAddress := GetMaxBalAddr(t, testClient, addresses)
if someAddress == "" {
t.Error("no addr with funds")
}
@@ -497,10 +375,10 @@ func TestAccountParticipationInfo(t *testing.T) {
stateproof.KeyLifetime = merklesignature.KeyLifetimeDefault
stateproof.Commitment[0] = 1 // change some byte so the stateproof is not considered empty (required since consensus v31)
- randomVotePKStr := randomString(32)
+ randomVotePKStr := RandomString(32)
var votePK crypto.OneTimeSignatureVerifier
copy(votePK[:], []byte(randomVotePKStr))
- randomSelPKStr := randomString(32)
+ randomSelPKStr := RandomString(32)
var selPK crypto.VRFVerifier
copy(selPK[:], []byte(randomSelPKStr))
var gh crypto.Digest
@@ -525,7 +403,7 @@ func TestAccountParticipationInfo(t *testing.T) {
}
txID, err := testClient.SignAndBroadcastTransaction(wh, nil, tx)
a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, txID, 30*time.Second)
+ _, err = WaitForTransaction(t, testClient, txID, 30*time.Second)
a.NoError(err)
account, err := testClient.AccountInformation(someAddress, false)
@@ -616,7 +494,7 @@ func TestSendingFromEmptyAccountFails(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
wh, err := testClient.GetUnencryptedWalletHandle()
a.NoError(err)
addresses, err := testClient.ListAddresses(wh)
@@ -656,7 +534,7 @@ func TestSendingTooLittleToEmptyAccountFails(t *testing.T) {
a := require.New(fixtures.SynchronizedTest(t))
defer fixture.SetTestContext(t)()
testClient := fixture.LibGoalClient
- waitForRoundOne(t, testClient)
+ WaitForRoundOne(t, testClient)
wh, err := testClient.GetUnencryptedWalletHandle()
a.NoError(err)
addresses, err := testClient.ListAddresses(wh)
@@ -674,7 +552,7 @@ func TestSendingTooLittleToEmptyAccountFails(t *testing.T) {
emptyAddress, err = testClient.GenerateAddress(wh)
a.NoError(err)
}
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
+ _, someAddress := GetMaxBalAddr(t, testClient, addresses)
if someAddress == "" {
t.Error("no addr with funds")
}
@@ -694,14 +572,14 @@ func TestSendingLowFeeFails(t *testing.T) {
addresses, err := testClient.ListAddresses(wh)
a.NoError(err)
const sendAmount = 100000
- someBal, someAddress := getMaxBalAddr(t, testClient, addresses)
+ someBal, someAddress := GetMaxBalAddr(t, testClient, addresses)
if someAddress == "" {
t.Error("no addr with funds")
}
if someBal < sendAmount {
t.Errorf("balance too low %d < %d", someBal, sendAmount)
}
- toAddress := getDestAddr(t, testClient, addresses, someAddress, wh)
+ toAddress := GetDestAddr(t, testClient, addresses, someAddress, wh)
utx, err := testClient.ConstructPayment(someAddress, toAddress, 1, sendAmount, nil, "", [32]byte{}, 0, 0)
a.NoError(err)
utx.Fee.Raw = 1
@@ -717,2745 +595,3 @@ func TestSendingLowFeeFails(t *testing.T) {
t.Log(err)
a.Error(err)
}
-
-func TestSendingNotClosingAccountFails(t *testing.T) {
- partitiontest.PartitionTest(t)
- defer fixtures.ShutdownSynchronizedTest(t)
-
- a := require.New(fixtures.SynchronizedTest(t))
- // use a local fixture because we might really mess with the balances
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
- defer localFixture.Shutdown()
- testClient := localFixture.LibGoalClient
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- var emptyAddress string
- for _, addr := range addresses {
- bal, err := testClient.GetBalance(addr)
- a.NoError(err)
- if bal == 0 {
- emptyAddress = addr
- break
- }
- }
- if emptyAddress == "" {
- emptyAddress, err = testClient.GenerateAddress(wh)
- a.NoError(err)
- }
- var someAddress string
- someBal := uint64(0)
- for _, addr := range addresses {
- if addr != emptyAddress {
- bal, err := testClient.GetBalance(addr)
- a.NoError(err)
- if bal > someBal {
- someAddress = addr
- someBal = bal
- }
- }
- }
- if someAddress == "" {
- t.Error("no addr with funds")
- }
- amt := someBal - 10000 - 1
- _, err = testClient.SendPaymentFromWallet(wh, nil, someAddress, emptyAddress, 10000, amt, nil, "", 0, 0)
- a.Error(err)
-}
-
-func TestClientCanGetPendingTransactions(t *testing.T) {
- partitiontest.PartitionTest(t)
- defer fixtures.ShutdownSynchronizedTest(t)
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
- wh, _ := testClient.GetUnencryptedWalletHandle()
- addresses, _ := testClient.ListAddresses(wh)
- fromAddress := addresses[0]
- toAddress, _ := testClient.GenerateAddress(wh)
- // We may not need to kill the other node, but do it anyways to ensure the txn never gets committed
- nc, _ := localFixture.GetNodeController("Node")
- err := nc.FullStop()
- a.NoError(err)
-
- minTxnFee, minAcctBalance, err := localFixture.CurrentMinFeeAndBalance()
- a.NoError(err)
-
- // Check that a single pending txn is corectly displayed
- tx, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress, minTxnFee, minAcctBalance, nil)
- a.NoError(err)
- statusResponse, err := testClient.GetParsedPendingTransactions(0)
- a.NoError(err)
- a.NotEmpty(statusResponse)
- a.True(statusResponse.TotalTransactions == 1)
- a.True(len(statusResponse.TopTransactions) == 1)
-
- // Parse response into SignedTxn
- pendingTxn := statusResponse.TopTransactions[0]
- a.True(pendingTxn.Txn.ID().String() == tx.ID().String())
-}
-
-func TestClientTruncatesPendingTransactions(t *testing.T) {
- partitiontest.PartitionTest(t)
- defer fixtures.ShutdownSynchronizedTest(t)
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
- wh, _ := testClient.GetUnencryptedWalletHandle()
- nc, _ := localFixture.GetNodeController("Node")
- err := nc.FullStop()
- a.NoError(err)
-
- minTxnFee, minAcctBalance, err := localFixture.CurrentMinFeeAndBalance()
- a.NoError(err)
-
- NumTxns := 10
- MaxTxns := 7
- addresses, _ := testClient.ListAddresses(wh)
- fromAddress := addresses[0]
- txIDsSeen := make(map[string]bool)
- for i := 0; i < NumTxns; i++ {
- toAddress, _ := testClient.GenerateAddress(wh)
- tx2, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress, minTxnFee, minAcctBalance, nil)
- a.NoError(err)
- txIDsSeen[tx2.ID().String()] = true
- }
- statusResponse, err := testClient.GetParsedPendingTransactions(uint64(MaxTxns))
- a.NoError(err)
- a.True(int(statusResponse.TotalTransactions) == NumTxns)
- a.True(len(statusResponse.TopTransactions) == MaxTxns)
- for _, tx := range statusResponse.TopTransactions {
- a.True(txIDsSeen[tx.Txn.ID().String()])
- delete(txIDsSeen, tx.Txn.ID().String())
- }
- a.True(len(txIDsSeen) == NumTxns-MaxTxns)
-}
-
-func TestClientPrioritizesPendingTransactions(t *testing.T) {
- partitiontest.PartitionTest(t)
- defer fixtures.ShutdownSynchronizedTest(t)
-
- t.Skip("new FIFO pool does not have prioritization")
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
- wh, _ := testClient.GetUnencryptedWalletHandle()
- addresses, _ := testClient.ListAddresses(wh)
- fromAddress := addresses[0]
- toAddress, _ := testClient.GenerateAddress(wh)
- nc, _ := localFixture.GetNodeController("Node")
- err := nc.FullStop()
- a.NoError(err)
-
- minTxnFee, minAcctBalance, err := localFixture.CurrentMinFeeAndBalance()
- a.NoError(err)
-
- NumTxns := 5
- MaxTxns := 3
- for i := 0; i < NumTxns; i++ {
- toAddress2, _ := testClient.GenerateAddress(wh)
- _, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress2, minTxnFee, minAcctBalance, nil)
- a.NoError(err)
- }
-
- // Add a very high fee transaction. This should have first priority
- // (even if we don't know the encoding length of the underlying signed txn)
- txHigh, err := testClient.SendPaymentFromUnencryptedWallet(fromAddress, toAddress, minTxnFee*10, minAcctBalance, nil)
- a.NoError(err)
-
- statusResponse, err := testClient.GetParsedPendingTransactions(uint64(MaxTxns))
- a.NoError(err)
- a.NotEmpty(statusResponse)
- a.True(int(statusResponse.TotalTransactions) == NumTxns+1)
- a.True(len(statusResponse.TopTransactions) == MaxTxns)
-
- pendingTxn := statusResponse.TopTransactions[0]
- a.True(pendingTxn.Txn.ID().String() == txHigh.ID().String())
-}
-
-func TestPendingTransactionInfoInnerTxnAssetCreate(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- testClient.WaitForRound(1)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
- if someAddress == "" {
- t.Error("no addr with funds")
- }
- a.NoError(err)
-
- prog := `#pragma version 5
-txn ApplicationID
-bz end
-itxn_begin
-int acfg
-itxn_field TypeEnum
-int 1000000
-itxn_field ConfigAssetTotal
-int 3
-itxn_field ConfigAssetDecimals
-byte "oz"
-itxn_field ConfigAssetUnitName
-byte "Gold"
-itxn_field ConfigAssetName
-byte "https://gold.rush/"
-itxn_field ConfigAssetURL
-byte 0x67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b
-itxn_field ConfigAssetMetadataHash
-itxn_submit
-end:
-int 1
-return
-`
- ops, err := logic.AssembleString(prog)
- a.NoError(err)
- approv := ops.Program
- ops, err = logic.AssembleString("#pragma version 5 \nint 1")
- clst := ops.Program
- a.NoError(err)
-
- gl := basics.StateSchema{}
- lc := basics.StateSchema{}
-
- // create app
- appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0)
- a.NoError(err)
- appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn)
- a.NoError(err)
- appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, appCreateTxID, 30*time.Second)
- a.NoError(err)
-
- // get app ID
- submittedAppCreateTxn, err := testClient.PendingTransactionInformation(appCreateTxID)
- a.NoError(err)
- a.NotNil(submittedAppCreateTxn.ApplicationIndex)
- createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
- a.NotZero(createdAppID)
-
- // fund app account
- appFundTxn, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, createdAppID.Address().String(), 0, 1_000_000, nil, "", 0, 0)
- a.NoError(err)
- appFundTxID := appFundTxn.ID()
- _, err = waitForTransaction(t, testClient, someAddress, appFundTxID.String(), 30*time.Second)
- a.NoError(err)
-
- // call app, which will issue an ASA create inner txn
- appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(uint64(createdAppID), nil, nil, nil, nil, nil)
- a.NoError(err)
- appCallTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn)
- a.NoError(err)
- appCallTxnTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCallTxn)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, appCallTxnTxID, 30*time.Second)
- a.NoError(err)
-
- // verify pending txn info of outer txn
- submittedAppCallTxn, err := testClient.PendingTransactionInformation(appCallTxnTxID)
- a.NoError(err)
- a.Nil(submittedAppCallTxn.ApplicationIndex)
- a.Nil(submittedAppCallTxn.AssetIndex)
- a.NotNil(submittedAppCallTxn.InnerTxns)
- a.Len(*submittedAppCallTxn.InnerTxns, 1)
-
- // verify pending txn info of inner txn
- innerTxn := (*submittedAppCallTxn.InnerTxns)[0]
- a.Nil(innerTxn.ApplicationIndex)
- a.NotNil(innerTxn.AssetIndex)
- createdAssetID := *innerTxn.AssetIndex
- a.NotZero(createdAssetID)
-
- createdAssetInfo, err := testClient.AssetInformation(createdAssetID)
- a.NoError(err)
- a.Equal(createdAssetID, createdAssetInfo.Index)
- a.Equal(createdAppID.Address().String(), createdAssetInfo.Params.Creator)
- a.Equal(uint64(1000000), createdAssetInfo.Params.Total)
- a.Equal(uint64(3), createdAssetInfo.Params.Decimals)
- a.Equal("oz", *createdAssetInfo.Params.UnitName)
- a.Equal("Gold", *createdAssetInfo.Params.Name)
- a.Equal("https://gold.rush/", *createdAssetInfo.Params.Url)
- expectedMetadata, err := hex.DecodeString("67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b")
- a.NoError(err)
- a.Equal(expectedMetadata, *createdAssetInfo.Params.MetadataHash)
-}
-
-func TestStateProofInParticipationInfo(t *testing.T) {
- partitiontest.PartitionTest(t)
- defer fixtures.ShutdownSynchronizedTest(t)
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
-
- proto := config.Consensus[protocol.ConsensusCurrentVersion]
- localFixture.SetConsensus(config.ConsensusProtocols{protocol.ConsensusCurrentVersion: proto})
-
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
- waitForRoundOne(t, testClient)
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
- a.NotEmpty(someAddress, "no addr with funds")
-
- addr, err := basics.UnmarshalChecksumAddress(someAddress)
- a.NoError(err)
-
- params, err := testClient.SuggestedParams()
- a.NoError(err)
-
- firstRound := basics.Round(params.LastRound + 1)
- lastRound := basics.Round(params.LastRound + 1000)
- dilution := uint64(100)
- randomVotePKStr := randomString(32)
- var votePK crypto.OneTimeSignatureVerifier
- copy(votePK[:], randomVotePKStr)
- randomSelPKStr := randomString(32)
- var selPK crypto.VRFVerifier
- copy(selPK[:], randomSelPKStr)
- var mssRoot [merklesignature.MerkleSignatureSchemeRootSize]byte
- randomRootStr := randomString(merklesignature.MerkleSignatureSchemeRootSize)
- copy(mssRoot[:], randomRootStr)
- var gh crypto.Digest
- copy(gh[:], params.GenesisHash)
-
- tx := transactions.Transaction{
- Type: protocol.KeyRegistrationTx,
- Header: transactions.Header{
- Sender: addr,
- Fee: basics.MicroAlgos{Raw: 10000},
- FirstValid: firstRound,
- LastValid: lastRound,
- GenesisHash: gh,
- },
- KeyregTxnFields: transactions.KeyregTxnFields{
- VotePK: votePK,
- SelectionPK: selPK,
- VoteFirst: firstRound,
- StateProofPK: mssRoot,
- VoteLast: lastRound,
- VoteKeyDilution: dilution,
- Nonparticipation: false,
- },
- }
- txID, err := testClient.SignAndBroadcastTransaction(wh, nil, tx)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, txID, 120*time.Second)
- a.NoError(err)
-
- account, err := testClient.AccountInformation(someAddress, false)
- a.NoError(err)
- a.NotNil(account.Participation.StateProofKey)
-
- actual := [merklesignature.MerkleSignatureSchemeRootSize]byte{}
- copy(actual[:], *account.Participation.StateProofKey)
- a.Equal(mssRoot, actual)
-}
-
-func TestStateProofParticipationKeysAPI(t *testing.T) {
- partitiontest.PartitionTest(t)
- defer fixtures.ShutdownSynchronizedTest(t)
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
-
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
- waitForRoundOne(t, testClient)
-
- partdb, err := db.MakeErasableAccessor(filepath.Join(testClient.DataDir(), "/..", "/Wallet1.0.3000.partkey"))
- a.NoError(err)
-
- partkey, err := account.RestoreParticipation(partdb)
- a.NoError(err)
-
- pRoot, err := testClient.GetParticipationKeys()
- a.NoError(err)
-
- actual := [merklesignature.MerkleSignatureSchemeRootSize]byte{}
- a.NotNil(pRoot[0].Key.StateProofKey)
- copy(actual[:], *pRoot[0].Key.StateProofKey)
- a.Equal(partkey.StateProofSecrets.GetVerifier().Commitment[:], actual[:])
-}
-
-func TestNilStateProofInParticipationInfo(t *testing.T) {
- partitiontest.PartitionTest(t)
- defer fixtures.ShutdownSynchronizedTest(t)
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
-
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachV30.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
- waitForRoundOne(t, testClient)
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
- a.NotEmpty(someAddress, "no addr with funds")
-
- addr, err := basics.UnmarshalChecksumAddress(someAddress)
- a.NoError(err)
-
- params, err := testClient.SuggestedParams()
- a.NoError(err)
-
- firstRound := basics.Round(1)
- lastRound := basics.Round(20)
- dilution := uint64(100)
- randomVotePKStr := randomString(32)
- var votePK crypto.OneTimeSignatureVerifier
- copy(votePK[:], []byte(randomVotePKStr))
- randomSelPKStr := randomString(32)
- var selPK crypto.VRFVerifier
- copy(selPK[:], []byte(randomSelPKStr))
- var gh crypto.Digest
- copy(gh[:], params.GenesisHash)
-
- tx := transactions.Transaction{
- Type: protocol.KeyRegistrationTx,
- Header: transactions.Header{
- Sender: addr,
- Fee: basics.MicroAlgos{Raw: 10000},
- FirstValid: firstRound,
- LastValid: lastRound,
- GenesisHash: gh,
- },
- KeyregTxnFields: transactions.KeyregTxnFields{
- VotePK: votePK,
- SelectionPK: selPK,
- VoteFirst: firstRound,
- VoteLast: lastRound,
- VoteKeyDilution: dilution,
- Nonparticipation: false,
- },
- }
- txID, err := testClient.SignAndBroadcastTransaction(wh, nil, tx)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, txID, 30*time.Second)
- a.NoError(err)
-
- account, err := testClient.AccountInformation(someAddress, false)
- a.NoError(err)
- a.Nil(account.Participation.StateProofKey)
-}
-
-func TestBoxNamesByAppID(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- testClient.WaitForRound(1)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, someAddress := getMaxBalAddr(t, testClient, addresses)
- if someAddress == "" {
- t.Error("no addr with funds")
- }
- a.NoError(err)
-
- prog := `#pragma version 8
- txn ApplicationID
- bz end // create the app
- txn NumAppArgs
- bz end // approve when no app args
- txn ApplicationArgs 0 // [arg[0]] // fails if no args && app already exists
- byte "create" // [arg[0], "create"] // create box named arg[1]
- == // [arg[0]=?="create"]
- bz del // "create" ? continue : goto del
- int 5 // [5]
- txn ApplicationArgs 1 // [5, arg[1]]
- swap
- box_create // [] // boxes: arg[1] -> [5]byte
- assert
- b end
-del: // delete box arg[1]
- txn ApplicationArgs 0 // [arg[0]]
- byte "delete" // [arg[0], "delete"]
- == // [arg[0]=?="delete"]
- bz set // "delete" ? continue : goto set
- txn ApplicationArgs 1 // [arg[1]]
- box_del // del boxes[arg[1]]
- assert
- b end
-set: // put arg[1] at start of box arg[0] ... so actually a _partial_ "set"
- txn ApplicationArgs 0 // [arg[0]]
- byte "set" // [arg[0], "set"]
- == // [arg[0]=?="set"]
- bz bad // "delete" ? continue : goto bad
- txn ApplicationArgs 1 // [arg[1]]
- int 0 // [arg[1], 0]
- txn ApplicationArgs 2 // [arg[1], 0, arg[2]]
- box_replace // [] // boxes: arg[1] -> replace(boxes[arg[1]], 0, arg[2])
- b end
-bad:
- err
-end:
- int 1
-`
- ops, err := logic.AssembleString(prog)
- a.NoError(err)
- approval := ops.Program
- ops, err = logic.AssembleString("#pragma version 8\nint 1")
- a.NoError(err)
- clearState := ops.Program
-
- gl := basics.StateSchema{}
- lc := basics.StateSchema{}
-
- // create app
- appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
- 0, nil, nil, nil,
- nil, nil, transactions.NoOpOC,
- approval, clearState, gl, lc, 0,
- )
- a.NoError(err)
- appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn)
- a.NoError(err)
- appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, appCreateTxID, 30*time.Second)
- a.NoError(err)
-
- // get app ID
- submittedAppCreateTxn, err := testClient.PendingTransactionInformation(appCreateTxID)
- a.NoError(err)
- a.NotNil(submittedAppCreateTxn.ApplicationIndex)
- createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
- a.NotZero(createdAppID)
-
- // fund app account
- appFundTxn, err := testClient.SendPaymentFromWallet(
- wh, nil, someAddress, createdAppID.Address().String(),
- 0, 10_000_000, nil, "", 0, 0,
- )
- a.NoError(err)
- appFundTxID := appFundTxn.ID()
- _, err = waitForTransaction(t, testClient, someAddress, appFundTxID.String(), 30*time.Second)
- a.NoError(err)
-
- createdBoxName := map[string]bool{}
- var createdBoxCount uint64 = 0
-
- // define operate box helper
- operateBoxAndSendTxn := func(operation string, boxNames []string, boxValues []string, errPrefix ...string) {
- txns := make([]transactions.Transaction, len(boxNames))
- txIDs := make(map[string]string, len(boxNames))
-
- for i := 0; i < len(boxNames); i++ {
- appArgs := [][]byte{
- []byte(operation),
- []byte(boxNames[i]),
- []byte(boxValues[i]),
- }
- boxRef := transactions.BoxRef{
- Name: []byte(boxNames[i]),
- Index: 0,
- }
-
- txns[i], err = testClient.MakeUnsignedAppNoOpTx(
- uint64(createdAppID), appArgs,
- nil, nil, nil,
- []transactions.BoxRef{boxRef},
- )
- a.NoError(err)
- txns[i], err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, txns[i])
- a.NoError(err)
- txIDs[txns[i].ID().String()] = someAddress
- }
-
- var gid crypto.Digest
- gid, err = testClient.GroupID(txns)
- a.NoError(err)
-
- stxns := make([]transactions.SignedTxn, len(boxNames))
- for i := 0; i < len(boxNames); i++ {
- txns[i].Group = gid
- wh, err = testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- stxns[i], err = testClient.SignTransactionWithWallet(wh, nil, txns[i])
- a.NoError(err)
- }
-
- err = testClient.BroadcastTransactionGroup(stxns)
- if len(errPrefix) == 0 {
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, txns[0].ID().String(), 30*time.Second)
- a.NoError(err)
- } else {
- a.ErrorContains(err, errPrefix[0])
- }
- }
-
- // `assertErrorResponse` confirms the _Result limit exceeded_ error response provides expected fields and values.
- assertErrorResponse := func(err error, expectedCount, requestedMax uint64) {
- a.Error(err)
- e := err.(client.HTTPError)
- a.Equal(400, e.StatusCode)
-
- var er *model.ErrorResponse
- err = protocol.DecodeJSON([]byte(e.ErrorString), &er)
- a.NoError(err)
- a.Equal("Result limit exceeded", er.Message)
- a.Equal(uint64(100000), ((*er.Data)["max-api-box-per-application"]).(uint64))
- a.Equal(requestedMax, ((*er.Data)["max"]).(uint64))
- a.Equal(expectedCount, ((*er.Data)["total-boxes"]).(uint64))
-
- a.Len(*er.Data, 3, fmt.Sprintf("error response (%v) contains unverified fields. Extend test for new fields.", *er.Data))
- }
-
- // `assertBoxCount` sanity checks that the REST API respects `expectedCount` through different queries against app ID = `createdAppID`.
- assertBoxCount := func(expectedCount uint64) {
- // Query without client-side limit.
- resp, err := testClient.ApplicationBoxes(uint64(createdAppID), 0)
- a.NoError(err)
- a.Len(resp.Boxes, int(expectedCount))
-
- // Query with requested max < expected expectedCount.
- _, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount-1)
- assertErrorResponse(err, expectedCount, expectedCount-1)
-
- // Query with requested max == expected expectedCount.
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount)
- a.NoError(err)
- a.Len(resp.Boxes, int(expectedCount))
-
- // Query with requested max > expected expectedCount.
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount+1)
- a.NoError(err)
- a.Len(resp.Boxes, int(expectedCount))
- }
-
- // helper function, take operation and a slice of box names
- // then submit transaction group containing all operations on box names
- // Then we check these boxes are appropriately created/deleted
- operateAndMatchRes := func(operation string, boxNames []string) {
- boxValues := make([]string, len(boxNames))
- if operation == "create" {
- for i, box := range boxNames {
- keyValid, ok := createdBoxName[box]
- a.False(ok && keyValid)
- boxValues[i] = ""
- }
- } else if operation == "delete" {
- for i, box := range boxNames {
- keyValid, ok := createdBoxName[box]
- a.True(keyValid == ok)
- boxValues[i] = ""
- }
- } else {
- a.Failf("Unknown operation %s", operation)
- }
-
- operateBoxAndSendTxn(operation, boxNames, boxValues)
-
- if operation == "create" {
- for _, box := range boxNames {
- createdBoxName[box] = true
- }
- createdBoxCount += uint64(len(boxNames))
- } else if operation == "delete" {
- for _, box := range boxNames {
- createdBoxName[box] = false
- }
- createdBoxCount -= uint64(len(boxNames))
- }
-
- var resp model.BoxesResponse
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), 0)
- a.NoError(err)
-
- expectedCreatedBoxes := make([]string, 0, createdBoxCount)
- for name, isCreate := range createdBoxName {
- if isCreate {
- expectedCreatedBoxes = append(expectedCreatedBoxes, name)
- }
- }
- sort.Strings(expectedCreatedBoxes)
-
- actualBoxes := make([]string, len(resp.Boxes))
- for i, box := range resp.Boxes {
- actualBoxes[i] = string(box.Name)
- }
- sort.Strings(actualBoxes)
-
- a.Equal(expectedCreatedBoxes, actualBoxes)
- }
-
- testingBoxNames := []string{
- ` `,
- ` `,
- ` ? = % ;`,
- `; DROP *;`,
- `OR 1 = 1;`,
- `" ; SELECT * FROM kvstore; DROP acctrounds; `,
- `背负青天而莫之夭阏者,而后乃今将图南。`,
- `於浩歌狂熱之際中寒﹔於天上看見深淵。`,
- `於一切眼中看見無所有﹔於無所希望中得救。`,
- `有一遊魂,化為長蛇,口有毒牙。`,
- `不以嚙人,自嚙其身,終以殞顛。`,
- `那些智力超常的人啊`,
- `认为已经,熟悉了云和闪电的脾气`,
- `就不再迷惑,就不必了解自己,世界和他人`,
- `每天只管,被微风吹拂,与猛虎谈情`,
- `他们从来,不需要楼梯,只有窗口`,
- `把一切交付于梦境,和优美的浪潮`,
- `在这颗行星所有的酒馆,青春自由似乎理所应得`,
- `面向涣散的未来,只唱情歌,看不到坦克`,
- `在科学和啤酒都不能安抚的夜晚`,
- `他们丢失了四季,惶惑之行开始`,
- `这颗行星所有的酒馆,无法听到远方的呼喊`,
- `野心勃勃的灯火,瞬间吞没黑暗的脸庞`,
- `b64:APj/AA==`,
- `str:123.3/aa\\0`,
- string([]byte{0, 255, 254, 254}),
- string([]byte{0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}),
- `; SELECT key from kvstore WHERE key LIKE %;`,
- `?&%!=`,
- "SELECT * FROM kvstore " + string([]byte{0, 0}) + " WHERE key LIKE %; ",
- string([]byte{'%', 'a', 'b', 'c', 0, 0, '%', 'a', '!'}),
- `
-`,
- `™£´´∂ƒ∂ƒßƒ©∑®ƒß∂†¬∆`,
- `∑´´˙©˚¬∆ßåƒ√¬`,
- }
-
- // Happy Vanilla paths:
- resp, err := testClient.ApplicationBoxes(uint64(createdAppID), 0)
- a.NoError(err)
- a.Empty(resp.Boxes)
-
- // Some Un-Happy / Non-Vanilla paths:
-
- // Even though the next box _does not exist_ as asserted by the error below,
- // querying it for boxes _DOES NOT ERROR_. There is no easy way to tell
- // the difference between non-existing boxes for an app that once existed
- // vs. an app the NEVER existed.
- nonexistantAppIndex := uint64(1337)
- _, err = testClient.ApplicationInformation(nonexistantAppIndex)
- a.ErrorContains(err, "application does not exist")
- resp, err = testClient.ApplicationBoxes(nonexistantAppIndex, 0)
- a.NoError(err)
- a.Len(resp.Boxes, 0)
-
- operateBoxAndSendTxn("create", []string{``}, []string{``}, "box names may not be zero length")
-
- for i := 0; i < len(testingBoxNames); i += 16 {
- var strSliceTest []string
- // grouping box names to operate, and create such boxes
- if i+16 >= len(testingBoxNames) {
- strSliceTest = testingBoxNames[i:]
- } else {
- strSliceTest = testingBoxNames[i : i+16]
- }
- operateAndMatchRes("create", strSliceTest)
- }
-
- assertBoxCount(uint64(len(testingBoxNames)))
-
- for i := 0; i < len(testingBoxNames); i += 16 {
- var strSliceTest []string
- // grouping box names to operate, and delete such boxes
- if i+16 >= len(testingBoxNames) {
- strSliceTest = testingBoxNames[i:]
- } else {
- strSliceTest = testingBoxNames[i : i+16]
- }
- operateAndMatchRes("delete", strSliceTest)
- }
-
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), 0)
- a.NoError(err)
- a.Empty(resp.Boxes)
-
- // Get Box value from box name
- encodeInt := func(n uint64) []byte {
- ibytes := make([]byte, 8)
- binary.BigEndian.PutUint64(ibytes, n)
- return ibytes
- }
-
- boxTests := []struct {
- name []byte
- encodedName string
- value []byte
- }{
- {[]byte("foo"), "str:foo", []byte("bar12")},
- {encodeInt(12321), "int:12321", []byte{0, 1, 254, 3, 2}},
- {[]byte{0, 248, 255, 32}, "b64:APj/IA==", []byte("lux56")},
- }
-
- for _, boxTest := range boxTests {
- // Box values are 5 bytes, as defined by the test TEAL program.
- operateBoxAndSendTxn("create", []string{string(boxTest.name)}, []string{""})
- operateBoxAndSendTxn("set", []string{string(boxTest.name)}, []string{string(boxTest.value)})
-
- currentRoundBeforeBoxes, err := testClient.CurrentRound()
- a.NoError(err)
- boxResponse, err := testClient.GetApplicationBoxByName(uint64(createdAppID), boxTest.encodedName)
- a.NoError(err)
- currentRoundAfterBoxes, err := testClient.CurrentRound()
- a.NoError(err)
- a.Equal(boxTest.name, boxResponse.Name)
- a.Equal(boxTest.value, boxResponse.Value)
- // To reduce flakiness, only check the round from boxes is within a range.
- a.GreaterOrEqual(boxResponse.Round, currentRoundBeforeBoxes)
- a.LessOrEqual(boxResponse.Round, currentRoundAfterBoxes)
- }
-
- const numberOfBoxesRemaining = uint64(3)
- assertBoxCount(numberOfBoxesRemaining)
-
- // Non-vanilla. Wasteful but correct. Can delete an app without first cleaning up its boxes.
- appAccountData, err := testClient.AccountData(createdAppID.Address().String())
- a.NoError(err)
- a.Equal(numberOfBoxesRemaining, appAccountData.TotalBoxes)
- a.Equal(uint64(30), appAccountData.TotalBoxBytes)
-
- // delete the app
- appDeleteTxn, err := testClient.MakeUnsignedAppDeleteTx(uint64(createdAppID), nil, nil, nil, nil, nil)
- a.NoError(err)
- appDeleteTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appDeleteTxn)
- a.NoError(err)
- appDeleteTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appDeleteTxn)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, someAddress, appDeleteTxID, 30*time.Second)
- a.NoError(err)
-
- _, err = testClient.ApplicationInformation(uint64(createdAppID))
- a.ErrorContains(err, "application does not exist")
-
- assertBoxCount(numberOfBoxesRemaining)
-}
-
-func TestSimulateTxnTracerDevMode(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "DevModeTxnTracerNetwork.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- _, err := testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- senderBalance, senderAddress := getMaxBalAddr(t, testClient, addresses)
- if senderAddress == "" {
- t.Error("no addr with funds")
- }
- a.NoError(err)
-
- toAddress := getDestAddr(t, testClient, nil, senderAddress, wh)
- closeToAddress := getDestAddr(t, testClient, nil, senderAddress, wh)
-
- // Ensure these accounts don't exist
- receiverBalance, err := testClient.GetBalance(toAddress)
- a.NoError(err)
- a.Zero(receiverBalance)
- closeToBalance, err := testClient.GetBalance(closeToAddress)
- a.NoError(err)
- a.Zero(closeToBalance)
-
- txn, err := testClient.ConstructPayment(senderAddress, toAddress, 0, senderBalance/2, nil, closeToAddress, [32]byte{}, 0, 0)
- a.NoError(err)
- stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn)
- a.NoError(err)
-
- currentRoundBeforeSimulate, err := testClient.CurrentRound()
- a.NoError(err)
-
- simulateRequest := v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {
- Txns: []transactions.SignedTxn{stxn},
- },
- },
- }
- result, err := testClient.SimulateTransactions(simulateRequest)
- a.NoError(err)
-
- currentAfterAfterSimulate, err := testClient.CurrentRound()
- a.NoError(err)
-
- // We can assert equality here since DevMode rounds are controlled by txn sends.
- a.Equal(result.LastRound, currentRoundBeforeSimulate)
- a.Equal(result.LastRound, currentAfterAfterSimulate)
-
- closingAmount := senderBalance - txn.Fee.Raw - txn.Amount.Raw
- expectedResult := v2.PreEncodedSimulateResponse{
- Version: 2,
- LastRound: result.LastRound, // checked above
- TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
- {
- Txns: []v2.PreEncodedSimulateTxnResult{
- {
- Txn: v2.PreEncodedTxInfo{
- Txn: stxn,
- ClosingAmount: &closingAmount,
- },
- },
- },
- },
- },
- }
- a.Equal(expectedResult, result)
-
- // Ensure the transaction did not actually get applied to the ledger
- receiverBalance, err = testClient.GetBalance(toAddress)
- a.NoError(err)
- a.Zero(receiverBalance)
- closeToBalance, err = testClient.GetBalance(closeToAddress)
- a.NoError(err)
- a.Zero(closeToBalance)
-}
-
-func TestSimulateTransaction(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- _, err := testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- senderBalance, senderAddress := getMaxBalAddr(t, testClient, addresses)
- if senderAddress == "" {
- t.Error("no addr with funds")
- }
- a.NoError(err)
-
- toAddress := getDestAddr(t, testClient, nil, senderAddress, wh)
- closeToAddress := getDestAddr(t, testClient, nil, senderAddress, wh)
-
- // Ensure these accounts don't exist
- receiverBalance, err := testClient.GetBalance(toAddress)
- a.NoError(err)
- a.Zero(receiverBalance)
- closeToBalance, err := testClient.GetBalance(closeToAddress)
- a.NoError(err)
- a.Zero(closeToBalance)
-
- txn, err := testClient.ConstructPayment(senderAddress, toAddress, 0, senderBalance/2, nil, closeToAddress, [32]byte{}, 0, 0)
- a.NoError(err)
- stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn)
- a.NoError(err)
-
- currentRoundBeforeSimulate, err := testClient.CurrentRound()
- a.NoError(err)
-
- simulateRequest := v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {
- Txns: []transactions.SignedTxn{stxn},
- },
- },
- }
- result, err := testClient.SimulateTransactions(simulateRequest)
- a.NoError(err)
-
- currentAfterAfterSimulate, err := testClient.CurrentRound()
- a.NoError(err)
-
- // To reduce flakiness, only check the round from simulate is within a range.
- a.GreaterOrEqual(result.LastRound, currentRoundBeforeSimulate)
- a.LessOrEqual(result.LastRound, currentAfterAfterSimulate)
-
- closingAmount := senderBalance - txn.Fee.Raw - txn.Amount.Raw
- expectedResult := v2.PreEncodedSimulateResponse{
- Version: 2,
- LastRound: result.LastRound, // checked above
- TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
- {
- Txns: []v2.PreEncodedSimulateTxnResult{
- {
- Txn: v2.PreEncodedTxInfo{
- Txn: stxn,
- ClosingAmount: &closingAmount,
- },
- },
- },
- },
- },
- }
- a.Equal(expectedResult, result)
-
- // Ensure the transaction did not actually get applied to the ledger
- receiverBalance, err = testClient.GetBalance(toAddress)
- a.NoError(err)
- a.Zero(receiverBalance)
- closeToBalance, err = testClient.GetBalance(closeToAddress)
- a.NoError(err)
- a.Zero(closeToBalance)
-}
-
-func TestSimulateWithOptionalSignatures(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- _, err := testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, senderAddress := getMaxBalAddr(t, testClient, addresses)
- if senderAddress == "" {
- t.Error("no addr with funds")
- }
- a.NoError(err)
-
- txn, err := testClient.ConstructPayment(senderAddress, senderAddress, 0, 1, nil, "", [32]byte{}, 0, 0)
- a.NoError(err)
-
- simulateRequest := v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {
- Txns: []transactions.SignedTxn{{Txn: txn}}, // no signature
- },
- },
- AllowEmptySignatures: true,
- }
- result, err := testClient.SimulateTransactions(simulateRequest)
- a.NoError(err)
-
- allowEmptySignatures := true
- expectedResult := v2.PreEncodedSimulateResponse{
- Version: 2,
- LastRound: result.LastRound,
- TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
- {
- Txns: []v2.PreEncodedSimulateTxnResult{
- {
- Txn: v2.PreEncodedTxInfo{
- Txn: transactions.SignedTxn{Txn: txn},
- },
- },
- },
- },
- },
- EvalOverrides: &model.SimulationEvalOverrides{
- AllowEmptySignatures: &allowEmptySignatures,
- },
- }
- a.Equal(expectedResult, result)
-}
-
-func TestSimulateWithUnlimitedLog(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- _, err := testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, senderAddress := getMaxBalAddr(t, testClient, addresses)
- if senderAddress == "" {
- t.Error("no addr with funds")
- }
- a.NoError(err)
-
- // construct program that uses a lot of log
- prog := `#pragma version 8
-txn NumAppArgs
-int 0
-==
-bnz final
-`
- for i := 0; i < 17; i++ {
- prog += `byte "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
-log
-`
- }
- prog += `final:
-int 1`
- ops, err := logic.AssembleString(prog)
- a.NoError(err)
- approval := ops.Program
- ops, err = logic.AssembleString("#pragma version 8\nint 1")
- a.NoError(err)
- clearState := ops.Program
-
- gl := basics.StateSchema{}
- lc := basics.StateSchema{}
-
- // create app
- appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
- 0, nil, nil, nil,
- nil, nil, transactions.NoOpOC,
- approval, clearState, gl, lc, 0,
- )
- a.NoError(err)
- appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
- a.NoError(err)
- // sign and broadcast
- appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
- a.NoError(err)
- submittedAppCreateTxn, err := waitForTransaction(t, testClient, senderAddress, appCreateTxID, 30*time.Second)
- a.NoError(err)
-
- // get app ID
- a.NotNil(submittedAppCreateTxn.ApplicationIndex)
- createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
- a.NotZero(createdAppID)
-
- // fund app account
- appFundTxn, err := testClient.SendPaymentFromWallet(
- wh, nil, senderAddress, createdAppID.Address().String(),
- 0, 10_000_000, nil, "", 0, 0,
- )
- a.NoError(err)
- appFundTxID := appFundTxn.ID()
- _, err = waitForTransaction(t, testClient, senderAddress, appFundTxID.String(), 30*time.Second)
- a.NoError(err)
-
- // construct app call
- appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(createdAppID), [][]byte{[]byte("first-arg")},
- nil, nil, nil, nil,
- )
- a.NoError(err)
- appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn)
- a.NoError(err)
- appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
- a.NoError(err)
-
- resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {
- Txns: []transactions.SignedTxn{appCallTxnSigned},
- },
- },
- AllowMoreLogging: true,
- })
- a.NoError(err)
-
- var logs [][]byte
- for i := 0; i < 17; i++ {
- logs = append(logs, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
- }
-
- budgetAdded, budgetUsed := uint64(700), uint64(40)
- maxLogSize, maxLogCalls := uint64(65536), uint64(2048)
-
- expectedResult := v2.PreEncodedSimulateResponse{
- Version: 2,
- LastRound: resp.LastRound,
- EvalOverrides: &model.SimulationEvalOverrides{
- MaxLogSize: &maxLogSize,
- MaxLogCalls: &maxLogCalls,
- },
- TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
- {
- Txns: []v2.PreEncodedSimulateTxnResult{
- {
- Txn: v2.PreEncodedTxInfo{
- Txn: appCallTxnSigned,
- Logs: &logs,
- },
- AppBudgetConsumed: &budgetUsed,
- },
- },
- AppBudgetAdded: &budgetAdded,
- AppBudgetConsumed: &budgetUsed,
- },
- },
- }
- a.Equal(expectedResult, resp)
-}
-
-func TestSimulateWithExtraBudget(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- _, err := testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, senderAddress := getMaxBalAddr(t, testClient, addresses)
- if senderAddress == "" {
- t.Error("no addr with funds")
- }
- a.NoError(err)
-
- // construct program that uses a lot of budget
- prog := `#pragma version 8
-txn ApplicationID
-bz end
-`
- prog += strings.Repeat(`int 1; pop; `, 700)
- prog += `end:
-int 1`
-
- ops, err := logic.AssembleString(prog)
- a.NoError(err)
- approval := ops.Program
- ops, err = logic.AssembleString("#pragma version 8\nint 1")
- a.NoError(err)
- clearState := ops.Program
-
- gl := basics.StateSchema{}
- lc := basics.StateSchema{}
-
- // create app
- appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
- 0, nil, nil, nil,
- nil, nil, transactions.NoOpOC,
- approval, clearState, gl, lc, 0,
- )
- a.NoError(err)
- appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
- a.NoError(err)
- // sign and broadcast
- appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
- a.NoError(err)
- submittedAppCreateTxn, err := waitForTransaction(t, testClient, senderAddress, appCreateTxID, 30*time.Second)
- a.NoError(err)
-
- // get app ID
- a.NotNil(submittedAppCreateTxn.ApplicationIndex)
- createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
- a.NotZero(createdAppID)
-
- // fund app account
- appFundTxn, err := testClient.SendPaymentFromWallet(
- wh, nil, senderAddress, createdAppID.Address().String(),
- 0, 10_000_000, nil, "", 0, 0,
- )
- a.NoError(err)
- appFundTxID := appFundTxn.ID()
- _, err = waitForTransaction(t, testClient, senderAddress, appFundTxID.String(), 30*time.Second)
- a.NoError(err)
-
- // construct app call
- appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(createdAppID), nil, nil, nil, nil, nil,
- )
- a.NoError(err)
- appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn)
- a.NoError(err)
- appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
- a.NoError(err)
-
- extraBudget := uint64(704)
- resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {
- Txns: []transactions.SignedTxn{appCallTxnSigned},
- },
- },
- ExtraOpcodeBudget: extraBudget,
- })
- a.NoError(err)
-
- budgetAdded, budgetUsed := uint64(1404), uint64(1404)
-
- expectedResult := v2.PreEncodedSimulateResponse{
- Version: 2,
- LastRound: resp.LastRound,
- EvalOverrides: &model.SimulationEvalOverrides{ExtraOpcodeBudget: &extraBudget},
- TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
- {
- Txns: []v2.PreEncodedSimulateTxnResult{
- {
- Txn: v2.PreEncodedTxInfo{Txn: appCallTxnSigned},
- AppBudgetConsumed: &budgetUsed,
- },
- },
- AppBudgetAdded: &budgetAdded,
- AppBudgetConsumed: &budgetUsed,
- },
- },
- }
- a.Equal(expectedResult, resp)
-}
-
-func toPtr[T any](constVar T) *T { return &constVar }
-
-func valToNil[T comparable](v *T) *T {
- var defaultV T
- if v == nil || *v == defaultV {
- return nil
- }
- return v
-}
-
-// The program is copied from pyteal source for c2c test over betanet:
-// source: https://github.com/ahangsu/c2c-testscript/blob/master/c2c_test/max_depth/app.py
-const maxDepthTealApproval = `#pragma version 8
-txn ApplicationID
-int 0
-==
-bnz main_l6
-txn NumAppArgs
-int 1
-==
-bnz main_l3
-err
-main_l3:
-global CurrentApplicationID
-app_params_get AppApprovalProgram
-store 1
-store 0
-global CurrentApplicationID
-app_params_get AppClearStateProgram
-store 3
-store 2
-global CurrentApplicationAddress
-acct_params_get AcctBalance
-store 5
-store 4
-load 1
-assert
-load 3
-assert
-load 5
-assert
-int 2
-txna ApplicationArgs 0
-btoi
-exp
-itob
-log
-txna ApplicationArgs 0
-btoi
-int 0
->
-bnz main_l5
-main_l4:
-int 1
-return
-main_l5:
-itxn_begin
- int appl
- itxn_field TypeEnum
- int 0
- itxn_field Fee
- load 0
- itxn_field ApprovalProgram
- load 2
- itxn_field ClearStateProgram
-itxn_submit
-itxn_begin
- int pay
- itxn_field TypeEnum
- int 0
- itxn_field Fee
- load 4
- int 100000
- -
- itxn_field Amount
- byte "appID"
- gitxn 0 CreatedApplicationID
- itob
- concat
- sha512_256
- itxn_field Receiver
-itxn_next
- int appl
- itxn_field TypeEnum
- txna ApplicationArgs 0
- btoi
- int 1
- -
- itob
- itxn_field ApplicationArgs
- itxn CreatedApplicationID
- itxn_field ApplicationID
- int 0
- itxn_field Fee
- int DeleteApplication
- itxn_field OnCompletion
-itxn_submit
-b main_l4
-main_l6:
-int 1
-return`
-
-func goValuesToAvmValues(goValues ...interface{}) *[]model.AvmValue {
- if len(goValues) == 0 {
- return nil
- }
-
- boolToUint64 := func(b bool) uint64 {
- if b {
- return 1
- }
- return 0
- }
-
- modelValues := make([]model.AvmValue, len(goValues))
- for i, goValue := range goValues {
- switch converted := goValue.(type) {
- case []byte:
- modelValues[i] = model.AvmValue{
- Type: uint64(basics.TealBytesType),
- Bytes: &converted,
- }
- case bool:
- convertedUint := boolToUint64(converted)
- modelValues[i] = model.AvmValue{
- Type: uint64(basics.TealUintType),
- Uint: valToNil(&convertedUint),
- }
- case int:
- convertedUint := uint64(converted)
- modelValues[i] = model.AvmValue{
- Type: uint64(basics.TealUintType),
- Uint: valToNil(&convertedUint),
- }
- case basics.AppIndex:
- convertedUint := uint64(converted)
- modelValues[i] = model.AvmValue{
- Type: uint64(basics.TealUintType),
- Uint: valToNil(&convertedUint),
- }
- case uint64:
- modelValues[i] = model.AvmValue{
- Type: uint64(basics.TealUintType),
- Uint: valToNil(&converted),
- }
- default:
- panic("unexpected type inferred from interface{}")
- }
- }
- return &modelValues
-}
-
-func TestMaxDepthAppWithPCandStackTrace(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json"))
-
- // Get primary node
- primaryNode, err := fixture.GetNodeController("Primary")
- a.NoError(err)
-
- fixture.Start()
- defer primaryNode.FullStop()
-
- // get lib goal client
- testClient := fixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode)
-
- _, err = testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, senderAddress := getMaxBalAddr(t, testClient, addresses)
- a.NotEmpty(senderAddress, "no addr with funds")
- a.NoError(err)
-
- ops, err := logic.AssembleString(maxDepthTealApproval)
- a.NoError(err)
- approval := ops.Program
- ops, err = logic.AssembleString("#pragma version 8\nint 1")
- a.NoError(err)
- clearState := ops.Program
-
- gl := basics.StateSchema{}
- lc := basics.StateSchema{}
-
- MaxDepth := 2
- MinFee := config.Consensus[protocol.ConsensusFuture].MinTxnFee
- MinBalance := config.Consensus[protocol.ConsensusFuture].MinBalance
-
- // create app and get the application ID
- appCreateTxn, err := testClient.MakeUnsignedAppCreateTx(
- transactions.NoOpOC, approval, clearState, gl,
- lc, nil, nil, nil, nil, nil, 0)
- a.NoError(err)
- appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
- a.NoError(err)
-
- appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
- a.NoError(err)
- submittedAppCreateTxn, err := waitForTransaction(t, testClient, senderAddress, appCreateTxID, 30*time.Second)
- a.NoError(err)
- futureAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
-
- // fund app account
- appFundTxn, err := testClient.SendPaymentFromWallet(
- wh, nil, senderAddress, futureAppID.Address().String(),
- 0, MinBalance*uint64(MaxDepth+1), nil, "", 0, 0,
- )
- a.NoError(err)
-
- uint64ToBytes := func(v uint64) []byte {
- b := make([]byte, 8)
- binary.BigEndian.PutUint64(b, v)
- return b
- }
-
- // construct app calls
- appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{uint64ToBytes(uint64(MaxDepth))}, nil, nil, nil, nil,
- )
- a.NoError(err)
- appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee*uint64(3*MaxDepth+2), appCallTxn)
- a.NoError(err)
-
- // Group the transactions, and start the simulation
- gid, err := testClient.GroupID([]transactions.Transaction{appFundTxn, appCallTxn})
- a.NoError(err)
- appFundTxn.Group = gid
- appCallTxn.Group = gid
-
- appFundTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appFundTxn)
- a.NoError(err)
- appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
- a.NoError(err)
-
- // The first simulation should not pass, for simulation return PC in config has not been activated
- execTraceConfig := simulation.ExecTraceConfig{
- Enable: true,
- Stack: true,
- }
- simulateRequest := v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {Txns: []transactions.SignedTxn{appFundTxnSigned, appCallTxnSigned}},
- },
- ExecTraceConfig: execTraceConfig,
- }
-
- _, err = testClient.SimulateTransactions(simulateRequest)
- var httpError client.HTTPError
- a.ErrorAs(err, &httpError)
- a.Equal(http.StatusBadRequest, httpError.StatusCode)
- a.Contains(httpError.ErrorString, "the local configuration of the node has `EnableDeveloperAPI` turned off, while requesting for execution trace")
-
- // update the configuration file to enable EnableDeveloperAPI
- err = primaryNode.FullStop()
- a.NoError(err)
- cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir())
- a.NoError(err)
- cfg.EnableDeveloperAPI = true
- err = cfg.SaveToDisk(primaryNode.GetDataDir())
- require.NoError(t, err)
- fixture.Start()
-
- resp, err := testClient.SimulateTransactions(simulateRequest)
- a.NoError(err)
-
- // Check expected == actual
- creationOpcodeTrace := []model.SimulationOpcodeTraceUnit{
- {
- Pc: 1,
- },
- // txn ApplicationID
- {
- Pc: 6,
- StackAdditions: goValuesToAvmValues(0),
- },
- // int 0
- {
- Pc: 8,
- StackAdditions: goValuesToAvmValues(0),
- },
- // ==
- {
- Pc: 9,
- StackPopCount: toPtr[uint64](2),
- StackAdditions: goValuesToAvmValues(1),
- },
- // bnz main_l6
- {
- Pc: 10,
- StackPopCount: toPtr[uint64](1),
- },
- // int 1
- {
- Pc: 149,
- StackAdditions: goValuesToAvmValues(1),
- },
- // return
- {
- Pc: 150,
- StackAdditions: goValuesToAvmValues(1),
- StackPopCount: toPtr[uint64](1),
- },
- }
-
- const NumArgs = 1
-
- recursiveLongOpcodeTrace := func(appID basics.AppIndex, layer int) *[]model.SimulationOpcodeTraceUnit {
- return &[]model.SimulationOpcodeTraceUnit{
- {
- Pc: 1,
- },
- // txn ApplicationID
- {
- Pc: 6,
- StackAdditions: goValuesToAvmValues(appID),
- },
- // int 0
- {
- Pc: 8,
- StackAdditions: goValuesToAvmValues(0),
- },
- // ==
- {
- Pc: 9,
- StackAdditions: goValuesToAvmValues(false),
- StackPopCount: toPtr[uint64](2),
- },
- // bnz main_l6
- {
- Pc: 10,
- StackPopCount: toPtr[uint64](1),
- },
- // txn NumAppArgs
- {
- Pc: 13,
- StackAdditions: goValuesToAvmValues(NumArgs),
- },
- // int 1
- {
- Pc: 15,
- StackAdditions: goValuesToAvmValues(1),
- },
- // ==
- {
- Pc: 16,
- StackPopCount: toPtr[uint64](2),
- StackAdditions: goValuesToAvmValues(true),
- },
- // bnz main_l3
- {
- Pc: 17,
- StackPopCount: toPtr[uint64](1),
- },
- // global CurrentApplicationID
- {
- Pc: 21,
- StackAdditions: goValuesToAvmValues(appID),
- },
- // app_params_get AppApprovalProgram
- {
- Pc: 23,
- StackAdditions: goValuesToAvmValues(approval, 1),
- StackPopCount: toPtr[uint64](1),
- },
- // store 1
- {
- Pc: 25,
- StackPopCount: toPtr[uint64](1),
- },
- // store 0
- {
- Pc: 27,
- StackPopCount: toPtr[uint64](1),
- },
- // global CurrentApplicationID
- {
- Pc: 29,
- StackAdditions: goValuesToAvmValues(appID),
- },
- // app_params_get AppClearStateProgram
- {
- Pc: 31,
- StackAdditions: goValuesToAvmValues(clearState, 1),
- StackPopCount: toPtr[uint64](1),
- },
- // store 3
- {
- Pc: 33,
- StackPopCount: toPtr[uint64](1),
- },
- // store 2
- {
- Pc: 35,
- StackPopCount: toPtr[uint64](1),
- },
- // global CurrentApplicationAddress
- {
- Pc: 37,
- StackAdditions: goValuesToAvmValues(crypto.Digest(appID.Address()).ToSlice()),
- },
- // acct_params_get AcctBalance
- {
- Pc: 39,
- StackAdditions: goValuesToAvmValues(uint64(3-layer)*MinBalance, 1),
- StackPopCount: toPtr[uint64](1),
- },
- // store 5
- {
- Pc: 41,
- StackPopCount: toPtr[uint64](1),
- },
- // store 4
- {
- Pc: 43,
- StackPopCount: toPtr[uint64](1),
- },
- // load 1
- {
- Pc: 45,
- StackAdditions: goValuesToAvmValues(1),
- },
- // assert
- {
- Pc: 47,
- StackPopCount: toPtr[uint64](1),
- },
- // load 3
- {
- Pc: 48,
- StackAdditions: goValuesToAvmValues(1),
- },
- // assert
- {
- Pc: 50,
- StackPopCount: toPtr[uint64](1),
- },
- // load 5
- {
- Pc: 51,
- StackAdditions: goValuesToAvmValues(1),
- },
- // assert
- {
- Pc: 53,
- StackPopCount: toPtr[uint64](1),
- },
- // int 2
- {
- Pc: 54,
- StackAdditions: goValuesToAvmValues(2),
- },
- // txna ApplicationArgs 0
- {
- Pc: 56,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
- },
- // btoi
- {
- Pc: 59,
- StackAdditions: goValuesToAvmValues(uint64(MaxDepth - layer)),
- StackPopCount: toPtr[uint64](1),
- },
- // exp
- {
- Pc: 60,
- StackAdditions: goValuesToAvmValues(1 << (MaxDepth - layer)),
- StackPopCount: toPtr[uint64](2),
- },
- // itob
- {
- Pc: 61,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(1 << uint64(MaxDepth-layer))),
- StackPopCount: toPtr[uint64](1),
- },
- // log
- {
- Pc: 62,
- StackPopCount: toPtr[uint64](1),
- },
- // txna ApplicationArgs 0
- {
- Pc: 63,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
- },
- // btoi
- {
- Pc: 66,
- StackAdditions: goValuesToAvmValues(MaxDepth - layer),
- StackPopCount: toPtr[uint64](1),
- },
- // int 0
- {
- Pc: 67,
- StackAdditions: goValuesToAvmValues(0),
- },
- // >
- {
- Pc: 68,
- StackAdditions: goValuesToAvmValues(MaxDepth-layer > 0),
- StackPopCount: toPtr[uint64](2),
- },
- // bnz main_l5
- {
- Pc: 69,
- StackPopCount: toPtr[uint64](1),
- },
- // itxn_begin
- {
- Pc: 74,
- },
- // int appl
- {
- Pc: 75,
- StackAdditions: goValuesToAvmValues(6),
- },
- // itxn_field TypeEnum
- {
- Pc: 76,
- StackPopCount: toPtr[uint64](1),
- },
- // int 0
- {
- Pc: 78,
- StackAdditions: goValuesToAvmValues(0),
- },
- // itxn_field Fee
- {
- Pc: 79,
- StackPopCount: toPtr[uint64](1),
- },
- // load 0
- {
- Pc: 81,
- StackAdditions: goValuesToAvmValues(approval),
- },
- // itxn_field ApprovalProgram
- {
- Pc: 83,
- StackPopCount: toPtr[uint64](1),
- },
- // load 2
- {
- Pc: 85,
- StackAdditions: goValuesToAvmValues(clearState),
- },
- // itxn_field ClearStateProgram
- {
- Pc: 87,
- StackPopCount: toPtr[uint64](1),
- },
- // itxn_submit
- {
- Pc: 89,
- SpawnedInners: &[]uint64{0},
- },
- // itxn_begin
- {
- Pc: 90,
- },
- // int pay
- {
- Pc: 91,
- StackAdditions: goValuesToAvmValues(1),
- },
- // itxn_field TypeEnum
- {
- Pc: 92,
- StackPopCount: toPtr[uint64](1),
- },
- // int 0
- {
- Pc: 94,
- StackAdditions: goValuesToAvmValues(0),
- },
- // itxn_field Fee
- {
- Pc: 95,
- StackPopCount: toPtr[uint64](1),
- },
- // load 4
- {
- Pc: 97,
- StackAdditions: goValuesToAvmValues(uint64(3-layer) * MinBalance),
- },
- // int 100000
- {
- Pc: 99,
- StackAdditions: goValuesToAvmValues(MinBalance),
- },
- // -
- {
- Pc: 103,
- StackPopCount: toPtr[uint64](2),
- StackAdditions: goValuesToAvmValues(uint64(2-layer) * MinBalance),
- },
- // itxn_field Amount
- {
- Pc: 104,
- StackPopCount: toPtr[uint64](1),
- },
- // byte "appID"
- {
- Pc: 106,
- StackAdditions: goValuesToAvmValues([]byte("appID")),
- },
- // gitxn 0 CreatedApplicationID
- {
- Pc: 113,
- StackAdditions: goValuesToAvmValues(appID + 3),
- },
- // itob
- {
- Pc: 116,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(appID) + 3)),
- StackPopCount: toPtr[uint64](1),
- },
- // concat
- {
- Pc: 117,
- StackAdditions: goValuesToAvmValues([]byte("appID" + string(uint64ToBytes(uint64(appID)+3)))),
- StackPopCount: toPtr[uint64](2),
- },
- // sha512_256
- {
- Pc: 118,
- StackAdditions: goValuesToAvmValues(crypto.Digest(basics.AppIndex(uint64(appID) + 3).Address()).ToSlice()),
- StackPopCount: toPtr[uint64](1),
- },
- // itxn_field Receiver
- {
- Pc: 119,
- StackPopCount: toPtr[uint64](1),
- },
- {
- Pc: 121,
- },
- // int appl
- {
- Pc: 122,
- StackAdditions: goValuesToAvmValues(6),
- },
- // itxn_field TypeEnum
- {
- Pc: 123,
- StackPopCount: toPtr[uint64](1),
- },
- // txna ApplicationArgs 0
- {
- Pc: 125,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
- },
- // btoi
- {
- Pc: 128,
- StackAdditions: goValuesToAvmValues(MaxDepth - layer),
- StackPopCount: toPtr[uint64](1),
- },
- // int 1
- {
- Pc: 129,
- StackAdditions: goValuesToAvmValues(1),
- },
- // -
- {
- Pc: 130,
- StackAdditions: goValuesToAvmValues(MaxDepth - layer - 1),
- StackPopCount: toPtr[uint64](2),
- },
- // itob
- {
- Pc: 131,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer - 1))),
- StackPopCount: toPtr[uint64](1),
- },
- // itxn_field ApplicationArgs
- {
- Pc: 132,
- StackPopCount: toPtr[uint64](1),
- },
- // itxn CreatedApplicationID
- {
- Pc: 134,
- StackAdditions: goValuesToAvmValues(appID + 3),
- },
- // itxn_field ApplicationID
- {
- Pc: 136,
- StackPopCount: toPtr[uint64](1),
- },
- // int 0
- {
- Pc: 138,
- StackAdditions: goValuesToAvmValues(0),
- },
- // itxn_field Fee
- {
- Pc: 139,
- StackPopCount: toPtr[uint64](1),
- },
- // int DeleteApplication
- {
- Pc: 141,
- StackAdditions: goValuesToAvmValues(5),
- },
- // itxn_field OnCompletion
- {
- Pc: 143,
- StackPopCount: toPtr[uint64](1),
- },
- // itxn_submit
- {
- Pc: 145,
- SpawnedInners: &[]uint64{1, 2},
- },
- // b main_l4
- {
- Pc: 146,
- },
- // int 1
- {
- Pc: 72,
- StackAdditions: goValuesToAvmValues(1),
- },
- // return
- {
- Pc: 73,
- StackAdditions: goValuesToAvmValues(1),
- StackPopCount: toPtr[uint64](1),
- },
- }
- }
-
- finalDepthTrace := func(appID basics.AppIndex, layer int) *[]model.SimulationOpcodeTraceUnit {
- return &[]model.SimulationOpcodeTraceUnit{
- {
- Pc: 1,
- },
- // txn ApplicationID
- {
- Pc: 6,
- StackAdditions: goValuesToAvmValues(appID),
- },
- // int 0
- {
- Pc: 8,
- StackAdditions: goValuesToAvmValues(0),
- },
- // ==
- {
- Pc: 9,
- StackAdditions: goValuesToAvmValues(false),
- StackPopCount: toPtr[uint64](2),
- },
- // bnz main_l6
- {
- Pc: 10,
- StackPopCount: toPtr[uint64](1),
- },
- // txn NumAppArgs
- {
- Pc: 13,
- StackAdditions: goValuesToAvmValues(NumArgs),
- },
- // int 1
- {
- Pc: 15,
- StackAdditions: goValuesToAvmValues(1),
- },
- // ==
- {
- Pc: 16,
- StackPopCount: toPtr[uint64](2),
- StackAdditions: goValuesToAvmValues(true),
- },
- // bnz main_l3
- {
- Pc: 17,
- StackPopCount: toPtr[uint64](1),
- },
- // global CurrentApplicationID
- {
- Pc: 21,
- StackAdditions: goValuesToAvmValues(appID),
- },
- // app_params_get AppApprovalProgram
- {
- Pc: 23,
- StackAdditions: goValuesToAvmValues(approval, 1),
- StackPopCount: toPtr[uint64](1),
- },
- // store 1
- {
- Pc: 25,
- StackPopCount: toPtr[uint64](1),
- },
- // store 0
- {
- Pc: 27,
- StackPopCount: toPtr[uint64](1),
- },
- // global CurrentApplicationID
- {
- Pc: 29,
- StackAdditions: goValuesToAvmValues(appID),
- },
- // app_params_get AppClearStateProgram
- {
- Pc: 31,
- StackAdditions: goValuesToAvmValues(clearState, 1),
- StackPopCount: toPtr[uint64](1),
- },
- // store 3
- {
- Pc: 33,
- StackPopCount: toPtr[uint64](1),
- },
- // store 2
- {
- Pc: 35,
- StackPopCount: toPtr[uint64](1),
- },
- // global CurrentApplicationAddress
- {
- Pc: 37,
- StackAdditions: goValuesToAvmValues(crypto.Digest(appID.Address()).ToSlice()),
- },
- // acct_params_get AcctBalance
- {
- Pc: 39,
- StackAdditions: goValuesToAvmValues(uint64(3-layer)*MinBalance, 1),
- StackPopCount: toPtr[uint64](1),
- },
- // store 5
- {
- Pc: 41,
- StackPopCount: toPtr[uint64](1),
- },
- // store 4
- {
- Pc: 43,
- StackPopCount: toPtr[uint64](1),
- },
- // load 1
- {
- Pc: 45,
- StackAdditions: goValuesToAvmValues(1),
- },
- // assert
- {
- Pc: 47,
- StackPopCount: toPtr[uint64](1),
- },
- // load 3
- {
- Pc: 48,
- StackAdditions: goValuesToAvmValues(1),
- },
- // assert
- {
- Pc: 50,
- StackPopCount: toPtr[uint64](1),
- },
- // load 5
- {
- Pc: 51,
- StackAdditions: goValuesToAvmValues(1),
- },
- // assert
- {
- Pc: 53,
- StackPopCount: toPtr[uint64](1),
- },
- // int 2
- {
- Pc: 54,
- StackAdditions: goValuesToAvmValues(2),
- },
- // txna ApplicationArgs 0
- {
- Pc: 56,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
- },
- // btoi
- {
- Pc: 59,
- StackAdditions: goValuesToAvmValues(uint64(MaxDepth - layer)),
- StackPopCount: toPtr[uint64](1),
- },
- // exp
- {
- Pc: 60,
- StackAdditions: goValuesToAvmValues(1 << (MaxDepth - layer)),
- StackPopCount: toPtr[uint64](2),
- },
- // itob
- {
- Pc: 61,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(1 << uint64(MaxDepth-layer))),
- StackPopCount: toPtr[uint64](1),
- },
- // log
- {
- Pc: 62,
- StackPopCount: toPtr[uint64](1),
- },
- // txna ApplicationArgs 0
- {
- Pc: 63,
- StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
- },
- // btoi
- {
- Pc: 66,
- StackAdditions: goValuesToAvmValues(MaxDepth - layer),
- StackPopCount: toPtr[uint64](1),
- },
- // int 0
- {
- Pc: 67,
- StackAdditions: goValuesToAvmValues(0),
- },
- // >
- {
- Pc: 68,
- StackAdditions: goValuesToAvmValues(MaxDepth-layer > 0),
- StackPopCount: toPtr[uint64](2),
- },
- // bnz main_l5
- {
- Pc: 69,
- StackPopCount: toPtr[uint64](1),
- },
- // int 1
- {
- Pc: 72,
- StackAdditions: goValuesToAvmValues(1),
- },
- // return
- {
- Pc: 73,
- StackAdditions: goValuesToAvmValues(1),
- StackPopCount: toPtr[uint64](1),
- },
- }
- }
-
- a.Len(resp.TxnGroups[0].Txns, 2)
- a.Nil(resp.TxnGroups[0].FailureMessage)
- a.Nil(resp.TxnGroups[0].FailedAt)
-
- a.Nil(resp.TxnGroups[0].Txns[0].TransactionTrace)
-
- expectedTraceSecondTxn := &model.SimulationTransactionExecTrace{
- ApprovalProgramTrace: recursiveLongOpcodeTrace(futureAppID, 0),
- InnerTrace: &[]model.SimulationTransactionExecTrace{
- {ApprovalProgramTrace: &creationOpcodeTrace},
- {},
- {
- ApprovalProgramTrace: recursiveLongOpcodeTrace(futureAppID+3, 1),
- InnerTrace: &[]model.SimulationTransactionExecTrace{
- {ApprovalProgramTrace: &creationOpcodeTrace},
- {},
- {ApprovalProgramTrace: finalDepthTrace(futureAppID+6, 2)},
- },
- },
- },
- }
- a.Equal(expectedTraceSecondTxn, resp.TxnGroups[0].Txns[1].TransactionTrace)
-
- a.Equal(execTraceConfig, resp.ExecTraceConfig)
-}
-
-func TestSimulateScratchSlotChange(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json"))
-
- // Get primary node
- primaryNode, err := fixture.GetNodeController("Primary")
- a.NoError(err)
-
- fixture.Start()
- defer primaryNode.FullStop()
-
- // get lib goal client
- testClient := fixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode)
-
- _, err = testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, senderAddress := getMaxBalAddr(t, testClient, addresses)
- a.NotEmpty(senderAddress, "no addr with funds")
- a.NoError(err)
-
- ops, err := logic.AssembleString(
- `#pragma version 8
- global CurrentApplicationID
- bz end
- int 1
- store 1
- load 1
- dup
- stores
- end:
- int 1`)
- a.NoError(err)
- approval := ops.Program
- ops, err = logic.AssembleString("#pragma version 8\nint 1")
- a.NoError(err)
- clearState := ops.Program
-
- gl := basics.StateSchema{}
- lc := basics.StateSchema{}
-
- MinFee := config.Consensus[protocol.ConsensusFuture].MinTxnFee
- MinBalance := config.Consensus[protocol.ConsensusFuture].MinBalance
-
- // create app and get the application ID
- appCreateTxn, err := testClient.MakeUnsignedAppCreateTx(
- transactions.NoOpOC, approval, clearState, gl,
- lc, nil, nil, nil, nil, nil, 0)
- a.NoError(err)
- appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
- a.NoError(err)
-
- appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
- a.NoError(err)
- submittedAppCreateTxn, err := waitForTransaction(t, testClient, senderAddress, appCreateTxID, 30*time.Second)
- a.NoError(err)
- futureAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
-
- // fund app account
- appFundTxn, err := testClient.SendPaymentFromWallet(
- wh, nil, senderAddress, futureAppID.Address().String(),
- 0, MinBalance, nil, "", 0, 0,
- )
- a.NoError(err)
-
- // construct app calls
- appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{}, nil, nil, nil, nil,
- )
- a.NoError(err)
- appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallTxn)
- a.NoError(err)
-
- // Group the transactions
- gid, err := testClient.GroupID([]transactions.Transaction{appFundTxn, appCallTxn})
- a.NoError(err)
- appFundTxn.Group = gid
- appCallTxn.Group = gid
-
- appFundTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appFundTxn)
- a.NoError(err)
- appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
- a.NoError(err)
-
- // construct simulation request, with scratch slot change enabled
- execTraceConfig := simulation.ExecTraceConfig{
- Enable: true,
- Scratch: true,
- }
- simulateRequest := v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {Txns: []transactions.SignedTxn{appFundTxnSigned, appCallTxnSigned}},
- },
- ExecTraceConfig: execTraceConfig,
- }
-
- // update the configuration file to enable EnableDeveloperAPI
- err = primaryNode.FullStop()
- a.NoError(err)
- cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir())
- a.NoError(err)
- cfg.EnableDeveloperAPI = true
- err = cfg.SaveToDisk(primaryNode.GetDataDir())
- require.NoError(t, err)
- fixture.Start()
-
- // simulate with wrong config (not enabled trace), see expected error
- _, err = testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {Txns: []transactions.SignedTxn{appFundTxnSigned, appCallTxnSigned}},
- },
- ExecTraceConfig: simulation.ExecTraceConfig{Scratch: true},
- })
- a.ErrorContains(err, "basic trace must be enabled when enabling scratch slot change tracing")
-
- // start real simulating
- resp, err := testClient.SimulateTransactions(simulateRequest)
- a.NoError(err)
-
- // check if resp match expected result
- a.Equal(execTraceConfig, resp.ExecTraceConfig)
- a.Len(resp.TxnGroups[0].Txns, 2)
- a.Nil(resp.TxnGroups[0].Txns[0].TransactionTrace)
- a.NotNil(resp.TxnGroups[0].Txns[1].TransactionTrace)
-
- expectedTraceSecondTxn := &model.SimulationTransactionExecTrace{
- ApprovalProgramTrace: &[]model.SimulationOpcodeTraceUnit{
- {Pc: 1},
- {Pc: 4},
- {Pc: 6},
- {Pc: 9},
- {
- Pc: 10,
- ScratchChanges: &[]model.ScratchChange{
- {
- Slot: 1,
- NewValue: model.AvmValue{
- Type: 2,
- Uint: toPtr[uint64](1),
- },
- },
- },
- },
- {Pc: 12},
- {Pc: 14},
- {
- Pc: 15,
- ScratchChanges: &[]model.ScratchChange{
- {
- Slot: 1,
- NewValue: model.AvmValue{
- Type: 2,
- Uint: toPtr[uint64](1),
- },
- },
- },
- },
- {Pc: 16},
- },
- }
- a.Equal(expectedTraceSecondTxn, resp.TxnGroups[0].Txns[1].TransactionTrace)
-}
-
-func TestSimulateWithUnnamedResources(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- a := require.New(fixtures.SynchronizedTest(t))
- var localFixture fixtures.RestClientFixture
- localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
- defer localFixture.Shutdown()
-
- testClient := localFixture.LibGoalClient
-
- _, err := testClient.WaitForRound(1)
- a.NoError(err)
-
- wh, err := testClient.GetUnencryptedWalletHandle()
- a.NoError(err)
- addresses, err := testClient.ListAddresses(wh)
- a.NoError(err)
- _, senderAddress := getMaxBalAddr(t, testClient, addresses)
- a.NotEmpty(senderAddress, "no addr with funds")
- a.NoError(err)
-
- otherAddress := getDestAddr(t, testClient, nil, senderAddress, wh)
-
- // fund otherAddress
- txn, err := testClient.SendPaymentFromWallet(
- wh, nil, senderAddress, otherAddress,
- 0, 1_000_000, nil, "", 0, 0,
- )
- a.NoError(err)
- txID := txn.ID().String()
- _, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second)
- a.NoError(err)
-
- // create asset
- txn, err = testClient.MakeUnsignedAssetCreateTx(100, false, "", "", "", "", "", "", "", nil, 0)
- a.NoError(err)
- txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
- a.NoError(err)
- // sign and broadcast
- txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
- a.NoError(err)
- confirmedTxn, err := waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second)
- a.NoError(err)
- // get asset ID
- a.NotNil(confirmedTxn.AssetIndex)
- assetID := *confirmedTxn.AssetIndex
- a.NotZero(assetID)
-
- // opt-in to asset
- txn, err = testClient.MakeUnsignedAssetSendTx(assetID, 0, otherAddress, "", "")
- a.NoError(err)
- txn, err = testClient.FillUnsignedTxTemplate(otherAddress, 0, 0, 0, txn)
- a.NoError(err)
- // sign and broadcast
- txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, otherAddress, txID, 30*time.Second)
- a.NoError(err)
-
- // transfer asset
- txn, err = testClient.MakeUnsignedAssetSendTx(assetID, 1, otherAddress, "", "")
- a.NoError(err)
- txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
- a.NoError(err)
- // sign and broadcast
- txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
- a.NoError(err)
- _, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second)
- a.NoError(err)
-
- ops, err := logic.AssembleString("#pragma version 9\n int 1")
- a.NoError(err)
- alwaysApprove := ops.Program
-
- gl := basics.StateSchema{}
- lc := basics.StateSchema{}
-
- // create app
- txn, err = testClient.MakeUnsignedAppCreateTx(transactions.OptInOC, alwaysApprove, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0)
- a.NoError(err)
- txn, err = testClient.FillUnsignedTxTemplate(otherAddress, 0, 0, 0, txn)
- a.NoError(err)
- // sign and broadcast
- txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
- a.NoError(err)
- confirmedTxn, err = waitForTransaction(t, testClient, otherAddress, txID, 30*time.Second)
- a.NoError(err)
- // get app ID
- a.NotNil(confirmedTxn.ApplicationIndex)
- otherAppID := basics.AppIndex(*confirmedTxn.ApplicationIndex)
- a.NotZero(otherAppID)
-
- prog := fmt.Sprintf(`#pragma version 9
-txn ApplicationID
-bz end
-
-addr %s // otherAddress
-store 0
-
-int %d // assetID
-store 1
-
-int %d // otherAppID
-store 2
-
-// Account access
-load 0 // otherAddress
-balance
-assert
-
-// Asset params access
-load 1 // assetID
-asset_params_get AssetTotal
-assert
-int 100
-==
-assert
-
-// Asset holding access
-load 0 // otherAddress
-load 1 // assetID
-asset_holding_get AssetBalance
-assert
-int 1
-==
-assert
-
-// App params access
-load 2 // otherAppID
-app_params_get AppCreator
-assert
-load 0 // otherAddress
-==
-assert
-
-// App local access
-load 0 // otherAddress
-load 2 // otherAppID
-app_opted_in
-assert
-
-// Box access
-byte "A"
-int 1025
-box_create
-assert
-
-end:
-int 1
-`, otherAddress, assetID, otherAppID)
-
- ops, err = logic.AssembleString(prog)
- a.NoError(err)
- approval := ops.Program
-
- // create app
- txn, err = testClient.MakeUnsignedAppCreateTx(transactions.NoOpOC, approval, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0)
- a.NoError(err)
- txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
- a.NoError(err)
- // sign and broadcast
- txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
- a.NoError(err)
- confirmedTxn, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second)
- a.NoError(err)
- // get app ID
- a.NotNil(confirmedTxn.ApplicationIndex)
- testAppID := basics.AppIndex(*confirmedTxn.ApplicationIndex)
- a.NotZero(testAppID)
-
- // fund app account
- txn, err = testClient.SendPaymentFromWallet(
- wh, nil, senderAddress, testAppID.Address().String(),
- 0, 1_000_000, nil, "", 0, 0,
- )
- a.NoError(err)
- txID = txn.ID().String()
- _, err = waitForTransaction(t, testClient, senderAddress, txID, 30*time.Second)
- a.NoError(err)
-
- // construct app call
- txn, err = testClient.MakeUnsignedAppNoOpTx(
- uint64(testAppID), nil, nil, nil, nil, nil,
- )
- a.NoError(err)
- txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
- a.NoError(err)
- stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn)
- a.NoError(err)
-
- // Cannot access these resources by default
- resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {
- Txns: []transactions.SignedTxn{stxn},
- },
- },
- AllowUnnamedResources: false,
- })
- a.NoError(err)
- a.Contains(*resp.TxnGroups[0].FailureMessage, "logic eval error: invalid Account reference "+otherAddress)
- a.Equal([]uint64{0}, *resp.TxnGroups[0].FailedAt)
-
- // It should work with AllowUnnamedResources=true
- resp, err = testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
- TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
- {
- Txns: []transactions.SignedTxn{stxn},
- },
- },
- AllowUnnamedResources: true,
- })
- a.NoError(err)
-
- expectedUnnamedGroupResources := model.SimulateUnnamedResourcesAccessed{
- Accounts: &[]string{otherAddress},
- Assets: &[]uint64{assetID},
- Apps: &[]uint64{uint64(otherAppID)},
- Boxes: &[]model.BoxReference{{App: uint64(testAppID), Name: []byte("A")}},
- ExtraBoxRefs: toPtr[uint64](1),
- AssetHoldings: &[]model.AssetHoldingReference{
- {Account: otherAddress, Asset: assetID},
- },
- AppLocals: &[]model.ApplicationLocalReference{
- {Account: otherAddress, App: uint64(otherAppID)},
- },
- }
-
- budgetAdded, budgetUsed := uint64(700), uint64(40)
- allowUnnamedResources := true
-
- expectedResult := v2.PreEncodedSimulateResponse{
- Version: 2,
- LastRound: resp.LastRound,
- EvalOverrides: &model.SimulationEvalOverrides{
- AllowUnnamedResources: &allowUnnamedResources,
- },
- TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
- {
- Txns: []v2.PreEncodedSimulateTxnResult{
- {
- Txn: v2.PreEncodedTxInfo{Txn: stxn},
- AppBudgetConsumed: &budgetUsed,
- },
- },
- AppBudgetAdded: &budgetAdded,
- AppBudgetConsumed: &budgetUsed,
- UnnamedResourcesAccessed: &expectedUnnamedGroupResources,
- },
- },
- }
- a.Equal(expectedResult, resp)
-}
diff --git a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go
new file mode 100644
index 0000000000..9f4b66d57b
--- /dev/null
+++ b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go
@@ -0,0 +1,2697 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package simulate
+
+import (
+ "encoding/binary"
+ "fmt"
+ "net/http"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/daemon/algod/api/client"
+ v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2"
+ "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/ledger/simulation"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+
+ helper "github.com/algorand/go-algorand/test/e2e-go/restAPI"
+)
+
+func TestSimulateTxnTracerDevMode(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "DevModeTxnTracerNetwork.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ _, err := testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ senderBalance, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if senderAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ toAddress := helper.GetDestAddr(t, testClient, nil, senderAddress, wh)
+ closeToAddress := helper.GetDestAddr(t, testClient, nil, senderAddress, wh)
+
+ // Ensure these accounts don't exist
+ receiverBalance, err := testClient.GetBalance(toAddress)
+ a.NoError(err)
+ a.Zero(receiverBalance)
+ closeToBalance, err := testClient.GetBalance(closeToAddress)
+ a.NoError(err)
+ a.Zero(closeToBalance)
+
+ txn, err := testClient.ConstructPayment(senderAddress, toAddress, 0, senderBalance/2, nil, closeToAddress, [32]byte{}, 0, 0)
+ a.NoError(err)
+ stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn)
+ a.NoError(err)
+
+ currentRoundBeforeSimulate, err := testClient.CurrentRound()
+ a.NoError(err)
+
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{stxn},
+ },
+ },
+ }
+ result, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ currentAfterAfterSimulate, err := testClient.CurrentRound()
+ a.NoError(err)
+
+ // We can assert equality here since DevMode rounds are controlled by txn sends.
+ a.Equal(result.LastRound, currentRoundBeforeSimulate)
+ a.Equal(result.LastRound, currentAfterAfterSimulate)
+
+ closingAmount := senderBalance - txn.Fee.Raw - txn.Amount.Raw
+ expectedResult := v2.PreEncodedSimulateResponse{
+ Version: 2,
+ LastRound: result.LastRound, // checked above
+ TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
+ {
+ Txns: []v2.PreEncodedSimulateTxnResult{
+ {
+ Txn: v2.PreEncodedTxInfo{
+ Txn: stxn,
+ ClosingAmount: &closingAmount,
+ },
+ },
+ },
+ },
+ },
+ }
+ a.Equal(expectedResult, result)
+
+ // Ensure the transaction did not actually get applied to the ledger
+ receiverBalance, err = testClient.GetBalance(toAddress)
+ a.NoError(err)
+ a.Zero(receiverBalance)
+ closeToBalance, err = testClient.GetBalance(closeToAddress)
+ a.NoError(err)
+ a.Zero(closeToBalance)
+}
+
+func TestSimulateTransaction(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ _, err := testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ senderBalance, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if senderAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ toAddress := helper.GetDestAddr(t, testClient, nil, senderAddress, wh)
+ closeToAddress := helper.GetDestAddr(t, testClient, nil, senderAddress, wh)
+
+ // Ensure these accounts don't exist
+ receiverBalance, err := testClient.GetBalance(toAddress)
+ a.NoError(err)
+ a.Zero(receiverBalance)
+ closeToBalance, err := testClient.GetBalance(closeToAddress)
+ a.NoError(err)
+ a.Zero(closeToBalance)
+
+ txn, err := testClient.ConstructPayment(senderAddress, toAddress, 0, senderBalance/2, nil, closeToAddress, [32]byte{}, 0, 0)
+ a.NoError(err)
+ stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn)
+ a.NoError(err)
+
+ currentRoundBeforeSimulate, err := testClient.CurrentRound()
+ a.NoError(err)
+
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{stxn},
+ },
+ },
+ }
+ result, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ currentAfterAfterSimulate, err := testClient.CurrentRound()
+ a.NoError(err)
+
+ // To reduce flakiness, only check the round from simulate is within a range.
+ a.GreaterOrEqual(result.LastRound, currentRoundBeforeSimulate)
+ a.LessOrEqual(result.LastRound, currentAfterAfterSimulate)
+
+ closingAmount := senderBalance - txn.Fee.Raw - txn.Amount.Raw
+ expectedResult := v2.PreEncodedSimulateResponse{
+ Version: 2,
+ LastRound: result.LastRound, // checked above
+ TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
+ {
+ Txns: []v2.PreEncodedSimulateTxnResult{
+ {
+ Txn: v2.PreEncodedTxInfo{
+ Txn: stxn,
+ ClosingAmount: &closingAmount,
+ },
+ },
+ },
+ },
+ },
+ }
+ a.Equal(expectedResult, result)
+
+ // Ensure the transaction did not actually get applied to the ledger
+ receiverBalance, err = testClient.GetBalance(toAddress)
+ a.NoError(err)
+ a.Zero(receiverBalance)
+ closeToBalance, err = testClient.GetBalance(closeToAddress)
+ a.NoError(err)
+ a.Zero(closeToBalance)
+}
+
+func TestSimulateStartRound(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ if testing.Short() {
+ t.Skip()
+ }
+ t.Parallel()
+ a := require.New(fixtures.SynchronizedTest(t))
+
+ var fixture fixtures.RestClientFixture
+ fixture.Setup(t, filepath.Join("nettemplates", "TwoNodesFollower100Second.json"))
+ defer fixture.Shutdown()
+
+ // Get controller for Primary node
+ nc, err := fixture.GetNodeController("Primary")
+ a.NoError(err)
+
+ testClient := fixture.LibGoalClient
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if senderAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ approvalSrc := `#pragma version 8
+global Round
+itob
+log
+int 1`
+ clearStateSrc := `#pragma version 8
+int 1`
+ ops, err := logic.AssembleString(approvalSrc)
+ a.NoError(err)
+ approval := ops.Program
+ ops, err = logic.AssembleString(clearStateSrc)
+ a.NoError(err)
+ clearState := ops.Program
+
+ txn, err := testClient.MakeUnsignedApplicationCallTx(
+ 0, nil, nil, nil,
+ nil, nil, transactions.NoOpOC,
+ approval, clearState, basics.StateSchema{}, basics.StateSchema{}, 0,
+ )
+ a.NoError(err)
+ txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 1, 1001, 0, txn)
+ a.NoError(err)
+ stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn)
+ a.NoError(err)
+
+ // Get controller for follower node
+ followControl, err := fixture.GetNodeController("Follower")
+ a.NoError(err)
+ followClient := fixture.GetAlgodClientForController(followControl)
+
+ // Set sync round on follower
+ followerSyncRound := uint64(4)
+ err = followClient.SetSyncRound(followerSyncRound)
+ a.NoError(err)
+
+ cfg, err := config.LoadConfigFromDisk(followControl.GetDataDir())
+ a.NoError(err)
+
+ // Let the primary node make some progress
+ primaryClient := fixture.GetAlgodClientForController(nc)
+ err = fixture.ClientWaitForRoundWithTimeout(primaryClient, followerSyncRound+uint64(cfg.MaxAcctLookback))
+ a.NoError(err)
+
+ // Let follower node progress as far as it can
+ err = fixture.ClientWaitForRoundWithTimeout(followClient, followerSyncRound+uint64(cfg.MaxAcctLookback)-1)
+ a.NoError(err)
+
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{stxn},
+ },
+ },
+ }
+
+ // Simulate transactions against the follower node
+ simulateTransactions := func(request v2.PreEncodedSimulateRequest) (result v2.PreEncodedSimulateResponse, err error) {
+ encodedRequest := protocol.EncodeReflect(&request)
+ var resp []byte
+ resp, err = followClient.RawSimulateRawTransaction(encodedRequest)
+ if err != nil {
+ return
+ }
+ err = protocol.DecodeReflect(resp, &result)
+ return
+ }
+
+ // Test default behavior (should use latest round available)
+ result, err := simulateTransactions(simulateRequest)
+ a.NoError(err)
+ a.Len(result.TxnGroups, 1)
+ a.Empty(result.TxnGroups[0].FailureMessage)
+ a.Len(result.TxnGroups[0].Txns, 1)
+ a.NotNil(result.TxnGroups[0].Txns[0].Txn.Logs)
+ a.Len(*result.TxnGroups[0].Txns[0].Txn.Logs, 1)
+ a.Equal(followerSyncRound+uint64(cfg.MaxAcctLookback), binary.BigEndian.Uint64((*result.TxnGroups[0].Txns[0].Txn.Logs)[0]))
+
+ // Test with previous rounds
+ for i := uint64(0); i < cfg.MaxAcctLookback; i++ {
+ simulateRequest.Round = basics.Round(followerSyncRound + i)
+ result, err = simulateTransactions(simulateRequest)
+ a.NoError(err)
+ a.Len(result.TxnGroups, 1)
+ a.Empty(result.TxnGroups[0].FailureMessage)
+ a.Len(result.TxnGroups[0].Txns, 1)
+ a.NotNil(result.TxnGroups[0].Txns[0].Txn.Logs)
+ a.Len(*result.TxnGroups[0].Txns[0].Txn.Logs, 1)
+ a.LessOrEqual(followerSyncRound+i+1, binary.BigEndian.Uint64((*result.TxnGroups[0].Txns[0].Txn.Logs)[0]))
+ }
+
+ // If the round is too far back, we should get an error saying so.
+ simulateRequest.Round = basics.Round(followerSyncRound - 3)
+ endTime := time.Now().Add(6 * time.Second)
+ for {
+ result, err = simulateTransactions(simulateRequest)
+ if err != nil || endTime.After(time.Now()) {
+ break
+ }
+ time.Sleep(500 * time.Millisecond)
+ }
+ if err == nil {
+ // NOTE: The ledger can have variability in when it commits rounds to the database. It's
+ // possible that older rounds are still available because of this. If so, let's bail on the
+ // test.
+ t.Logf("Still producing a result for round %d", simulateRequest.Round)
+ return
+ }
+ var httpErr client.HTTPError
+ a.ErrorAs(err, &httpErr)
+ a.Equal(http.StatusInternalServerError, httpErr.StatusCode)
+ a.Contains(httpErr.ErrorString, fmt.Sprintf("round %d before dbRound", simulateRequest.Round))
+}
+
+func TestSimulateWithOptionalSignatures(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ _, err := testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if senderAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ txn, err := testClient.ConstructPayment(senderAddress, senderAddress, 0, 1, nil, "", [32]byte{}, 0, 0)
+ a.NoError(err)
+
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{{Txn: txn}}, // no signature
+ },
+ },
+ AllowEmptySignatures: true,
+ }
+ result, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ allowEmptySignatures := true
+ expectedResult := v2.PreEncodedSimulateResponse{
+ Version: 2,
+ LastRound: result.LastRound,
+ TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
+ {
+ Txns: []v2.PreEncodedSimulateTxnResult{
+ {
+ Txn: v2.PreEncodedTxInfo{
+ Txn: transactions.SignedTxn{Txn: txn},
+ },
+ },
+ },
+ },
+ },
+ EvalOverrides: &model.SimulationEvalOverrides{
+ AllowEmptySignatures: &allowEmptySignatures,
+ },
+ }
+ a.Equal(expectedResult, result)
+}
+
+func TestSimulateWithUnlimitedLog(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ _, err := testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if senderAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ // construct program that uses a lot of log
+ prog := `#pragma version 8
+txn NumAppArgs
+int 0
+==
+bnz final
+`
+ for i := 0; i < 17; i++ {
+ prog += `byte "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+log
+`
+ }
+ prog += `final:
+int 1`
+ ops, err := logic.AssembleString(prog)
+ a.NoError(err)
+ approval := ops.Program
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ // create app
+ appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
+ 0, nil, nil, nil,
+ nil, nil, transactions.NoOpOC,
+ approval, clearState, gl, lc, 0,
+ )
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+ // sign and broadcast
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ submittedAppCreateTxn, err := helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+
+ // get app ID
+ a.NotNil(submittedAppCreateTxn.ApplicationIndex)
+ createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+ a.NotZero(createdAppID)
+
+ // fund app account
+ appFundTxn, err := testClient.SendPaymentFromWallet(
+ wh, nil, senderAddress, createdAppID.Address().String(),
+ 0, 10_000_000, nil, "", 0, 0,
+ )
+ a.NoError(err)
+ appFundTxID := appFundTxn.ID()
+ _, err = helper.WaitForTransaction(t, testClient, appFundTxID.String(), 30*time.Second)
+ a.NoError(err)
+
+ // construct app call
+ appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(createdAppID), [][]byte{[]byte("first-arg")},
+ nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn)
+ a.NoError(err)
+ appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
+ a.NoError(err)
+
+ resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{appCallTxnSigned},
+ },
+ },
+ AllowMoreLogging: true,
+ })
+ a.NoError(err)
+
+ var logs [][]byte
+ for i := 0; i < 17; i++ {
+ logs = append(logs, []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
+ }
+
+ budgetAdded, budgetUsed := uint64(700), uint64(40)
+ maxLogSize, maxLogCalls := uint64(65536), uint64(2048)
+
+ expectedResult := v2.PreEncodedSimulateResponse{
+ Version: 2,
+ LastRound: resp.LastRound,
+ EvalOverrides: &model.SimulationEvalOverrides{
+ MaxLogSize: &maxLogSize,
+ MaxLogCalls: &maxLogCalls,
+ },
+ TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
+ {
+ Txns: []v2.PreEncodedSimulateTxnResult{
+ {
+ Txn: v2.PreEncodedTxInfo{
+ Txn: appCallTxnSigned,
+ Logs: &logs,
+ },
+ AppBudgetConsumed: &budgetUsed,
+ },
+ },
+ AppBudgetAdded: &budgetAdded,
+ AppBudgetConsumed: &budgetUsed,
+ },
+ },
+ }
+ a.Equal(expectedResult, resp)
+}
+
+func TestSimulateWithExtraBudget(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ _, err := testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ if senderAddress == "" {
+ t.Error("no addr with funds")
+ }
+ a.NoError(err)
+
+ // construct program that uses a lot of budget
+ prog := `#pragma version 8
+txn ApplicationID
+bz end
+`
+ prog += strings.Repeat(`int 1; pop; `, 700)
+ prog += `end:
+int 1`
+
+ ops, err := logic.AssembleString(prog)
+ a.NoError(err)
+ approval := ops.Program
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ // create app
+ appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
+ 0, nil, nil, nil,
+ nil, nil, transactions.NoOpOC,
+ approval, clearState, gl, lc, 0,
+ )
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+ // sign and broadcast
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ submittedAppCreateTxn, err := helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+
+ // get app ID
+ a.NotNil(submittedAppCreateTxn.ApplicationIndex)
+ createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+ a.NotZero(createdAppID)
+
+ // fund app account
+ appFundTxn, err := testClient.SendPaymentFromWallet(
+ wh, nil, senderAddress, createdAppID.Address().String(),
+ 0, 10_000_000, nil, "", 0, 0,
+ )
+ a.NoError(err)
+ appFundTxID := appFundTxn.ID()
+ _, err = helper.WaitForTransaction(t, testClient, appFundTxID.String(), 30*time.Second)
+ a.NoError(err)
+
+ // construct app call
+ appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(createdAppID), nil, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn)
+ a.NoError(err)
+ appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
+ a.NoError(err)
+
+ extraBudget := uint64(704)
+ resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{appCallTxnSigned},
+ },
+ },
+ ExtraOpcodeBudget: extraBudget,
+ })
+ a.NoError(err)
+
+ budgetAdded, budgetUsed := uint64(1404), uint64(1404)
+
+ expectedResult := v2.PreEncodedSimulateResponse{
+ Version: 2,
+ LastRound: resp.LastRound,
+ EvalOverrides: &model.SimulationEvalOverrides{ExtraOpcodeBudget: &extraBudget},
+ TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
+ {
+ Txns: []v2.PreEncodedSimulateTxnResult{
+ {
+ Txn: v2.PreEncodedTxInfo{Txn: appCallTxnSigned},
+ AppBudgetConsumed: &budgetUsed,
+ },
+ },
+ AppBudgetAdded: &budgetAdded,
+ AppBudgetConsumed: &budgetUsed,
+ },
+ },
+ }
+ a.Equal(expectedResult, resp)
+}
+
+func toPtr[T any](constVar T) *T { return &constVar }
+
+func valToNil[T comparable](v *T) *T {
+ var defaultV T
+ if v == nil || *v == defaultV {
+ return nil
+ }
+ return v
+}
+
+// The program is copied from pyteal source for c2c test over betanet:
+// source: https://github.com/ahangsu/c2c-testscript/blob/master/c2c_test/max_depth/app.py
+const maxDepthTealApproval = `#pragma version 8
+txn ApplicationID
+int 0
+==
+bnz main_l6
+txn NumAppArgs
+int 1
+==
+bnz main_l3
+err
+main_l3:
+global CurrentApplicationID
+app_params_get AppApprovalProgram
+store 1
+store 0
+global CurrentApplicationID
+app_params_get AppClearStateProgram
+store 3
+store 2
+global CurrentApplicationAddress
+acct_params_get AcctBalance
+store 5
+store 4
+load 1
+assert
+load 3
+assert
+load 5
+assert
+int 2
+txna ApplicationArgs 0
+btoi
+exp
+itob
+log
+txna ApplicationArgs 0
+btoi
+int 0
+>
+bnz main_l5
+main_l4:
+int 1
+return
+main_l5:
+itxn_begin
+ int appl
+ itxn_field TypeEnum
+ int 0
+ itxn_field Fee
+ load 0
+ itxn_field ApprovalProgram
+ load 2
+ itxn_field ClearStateProgram
+itxn_submit
+itxn_begin
+ int pay
+ itxn_field TypeEnum
+ int 0
+ itxn_field Fee
+ load 4
+ int 100000
+ -
+ itxn_field Amount
+ byte "appID"
+ gitxn 0 CreatedApplicationID
+ itob
+ concat
+ sha512_256
+ itxn_field Receiver
+itxn_next
+ int appl
+ itxn_field TypeEnum
+ txna ApplicationArgs 0
+ btoi
+ int 1
+ -
+ itob
+ itxn_field ApplicationArgs
+ itxn CreatedApplicationID
+ itxn_field ApplicationID
+ int 0
+ itxn_field Fee
+ int DeleteApplication
+ itxn_field OnCompletion
+itxn_submit
+b main_l4
+main_l6:
+int 1
+return`
+
+func goValuesToAvmValues(goValues ...interface{}) *[]model.AvmValue {
+ if len(goValues) == 0 {
+ return nil
+ }
+
+ boolToUint64 := func(b bool) uint64 {
+ if b {
+ return 1
+ }
+ return 0
+ }
+
+ modelValues := make([]model.AvmValue, len(goValues))
+ for i, goValue := range goValues {
+ switch converted := goValue.(type) {
+ case []byte:
+ modelValues[i] = model.AvmValue{
+ Type: uint64(basics.TealBytesType),
+ Bytes: &converted,
+ }
+ case bool:
+ convertedUint := boolToUint64(converted)
+ modelValues[i] = model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: valToNil(&convertedUint),
+ }
+ case int:
+ convertedUint := uint64(converted)
+ modelValues[i] = model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: valToNil(&convertedUint),
+ }
+ case basics.AppIndex:
+ convertedUint := uint64(converted)
+ modelValues[i] = model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: valToNil(&convertedUint),
+ }
+ case uint64:
+ modelValues[i] = model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: valToNil(&converted),
+ }
+ default:
+ panic("unexpected type inferred from interface{}")
+ }
+ }
+ return &modelValues
+}
+
+func TestMaxDepthAppWithPCandStackTrace(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json"))
+
+ primaryNode, err := localFixture.GetNodeController("Primary")
+ a.NoError(err)
+
+ localFixture.Start()
+ defer primaryNode.FullStop()
+
+ // get lib goal client
+ testClient := localFixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode)
+
+ _, err = testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(senderAddress, "no addr with funds")
+ a.NoError(err)
+
+ ops, err := logic.AssembleString(maxDepthTealApproval)
+ a.NoError(err)
+ approval := ops.Program
+ approvalHash := crypto.Hash(approval)
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ MaxDepth := 2
+ MinFee := config.Consensus[protocol.ConsensusFuture].MinTxnFee
+ MinBalance := config.Consensus[protocol.ConsensusFuture].MinBalance
+
+ // create app and get the application ID
+ appCreateTxn, err := testClient.MakeUnsignedAppCreateTx(
+ transactions.NoOpOC, approval, clearState, gl,
+ lc, nil, nil, nil, nil, nil, 0)
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ submittedAppCreateTxn, err := helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+ futureAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+
+ // fund app account
+ appFundTxn, err := testClient.ConstructPayment(
+ senderAddress, futureAppID.Address().String(),
+ 0, MinBalance*uint64(MaxDepth+1), nil, "", [32]byte{}, 0, 0,
+ )
+ a.NoError(err)
+
+ uint64ToBytes := func(v uint64) []byte {
+ b := make([]byte, 8)
+ binary.BigEndian.PutUint64(b, v)
+ return b
+ }
+
+ // construct app calls
+ appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(futureAppID), [][]byte{uint64ToBytes(uint64(MaxDepth))}, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee*uint64(3*MaxDepth+2), appCallTxn)
+ a.NoError(err)
+
+ // Group the transactions, and start the simulation
+ gid, err := testClient.GroupID([]transactions.Transaction{appFundTxn, appCallTxn})
+ a.NoError(err)
+ appFundTxn.Group = gid
+ appCallTxn.Group = gid
+
+ appFundTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appFundTxn)
+ a.NoError(err)
+ appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
+ a.NoError(err)
+
+ // The first simulation should not pass, for simulation return PC in config has not been activated
+ execTraceConfig := simulation.ExecTraceConfig{
+ Enable: true,
+ Stack: true,
+ }
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {Txns: []transactions.SignedTxn{appFundTxnSigned, appCallTxnSigned}},
+ },
+ ExecTraceConfig: execTraceConfig,
+ }
+
+ _, err = testClient.SimulateTransactions(simulateRequest)
+ var httpError client.HTTPError
+ a.ErrorAs(err, &httpError)
+ a.Equal(http.StatusBadRequest, httpError.StatusCode)
+ a.Contains(httpError.ErrorString, "the local configuration of the node has `EnableDeveloperAPI` turned off, while requesting for execution trace")
+
+ // update the configuration file to enable EnableDeveloperAPI
+ err = primaryNode.FullStop()
+ a.NoError(err)
+ cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir())
+ a.NoError(err)
+ cfg.EnableDeveloperAPI = true
+ err = cfg.SaveToDisk(primaryNode.GetDataDir())
+ require.NoError(t, err)
+ localFixture.Start()
+
+ resp, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ // Check expected == actual
+ creationOpcodeTrace := []model.SimulationOpcodeTraceUnit{
+ {
+ Pc: 1,
+ },
+ // txn ApplicationID
+ {
+ Pc: 6,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // int 0
+ {
+ Pc: 8,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // ==
+ {
+ Pc: 9,
+ StackPopCount: toPtr[uint64](2),
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // bnz main_l6
+ {
+ Pc: 10,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 1
+ {
+ Pc: 149,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // return
+ {
+ Pc: 150,
+ StackAdditions: goValuesToAvmValues(1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ }
+
+ const NumArgs = 1
+
+ recursiveLongOpcodeTrace := func(appID basics.AppIndex, layer int) *[]model.SimulationOpcodeTraceUnit {
+ return &[]model.SimulationOpcodeTraceUnit{
+ {
+ Pc: 1,
+ },
+ // txn ApplicationID
+ {
+ Pc: 6,
+ StackAdditions: goValuesToAvmValues(appID),
+ },
+ // int 0
+ {
+ Pc: 8,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // ==
+ {
+ Pc: 9,
+ StackAdditions: goValuesToAvmValues(false),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // bnz main_l6
+ {
+ Pc: 10,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // txn NumAppArgs
+ {
+ Pc: 13,
+ StackAdditions: goValuesToAvmValues(NumArgs),
+ },
+ // int 1
+ {
+ Pc: 15,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // ==
+ {
+ Pc: 16,
+ StackPopCount: toPtr[uint64](2),
+ StackAdditions: goValuesToAvmValues(true),
+ },
+ // bnz main_l3
+ {
+ Pc: 17,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // global CurrentApplicationID
+ {
+ Pc: 21,
+ StackAdditions: goValuesToAvmValues(appID),
+ },
+ // app_params_get AppApprovalProgram
+ {
+ Pc: 23,
+ StackAdditions: goValuesToAvmValues(approval, 1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 1
+ {
+ Pc: 25,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 0
+ {
+ Pc: 27,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // global CurrentApplicationID
+ {
+ Pc: 29,
+ StackAdditions: goValuesToAvmValues(appID),
+ },
+ // app_params_get AppClearStateProgram
+ {
+ Pc: 31,
+ StackAdditions: goValuesToAvmValues(clearState, 1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 3
+ {
+ Pc: 33,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 2
+ {
+ Pc: 35,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // global CurrentApplicationAddress
+ {
+ Pc: 37,
+ StackAdditions: goValuesToAvmValues(crypto.Digest(appID.Address()).ToSlice()),
+ },
+ // acct_params_get AcctBalance
+ {
+ Pc: 39,
+ StackAdditions: goValuesToAvmValues(uint64(3-layer)*MinBalance, 1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 5
+ {
+ Pc: 41,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 4
+ {
+ Pc: 43,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 1
+ {
+ Pc: 45,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // assert
+ {
+ Pc: 47,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 3
+ {
+ Pc: 48,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // assert
+ {
+ Pc: 50,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 5
+ {
+ Pc: 51,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // assert
+ {
+ Pc: 53,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 2
+ {
+ Pc: 54,
+ StackAdditions: goValuesToAvmValues(2),
+ },
+ // txna ApplicationArgs 0
+ {
+ Pc: 56,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
+ },
+ // btoi
+ {
+ Pc: 59,
+ StackAdditions: goValuesToAvmValues(uint64(MaxDepth - layer)),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // exp
+ {
+ Pc: 60,
+ StackAdditions: goValuesToAvmValues(1 << (MaxDepth - layer)),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // itob
+ {
+ Pc: 61,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(1 << uint64(MaxDepth-layer))),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // log
+ {
+ Pc: 62,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // txna ApplicationArgs 0
+ {
+ Pc: 63,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
+ },
+ // btoi
+ {
+ Pc: 66,
+ StackAdditions: goValuesToAvmValues(MaxDepth - layer),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 0
+ {
+ Pc: 67,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // >
+ {
+ Pc: 68,
+ StackAdditions: goValuesToAvmValues(MaxDepth-layer > 0),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // bnz main_l5
+ {
+ Pc: 69,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // itxn_begin
+ {
+ Pc: 74,
+ },
+ // int appl
+ {
+ Pc: 75,
+ StackAdditions: goValuesToAvmValues(6),
+ },
+ // itxn_field TypeEnum
+ {
+ Pc: 76,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 0
+ {
+ Pc: 78,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // itxn_field Fee
+ {
+ Pc: 79,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 0
+ {
+ Pc: 81,
+ StackAdditions: goValuesToAvmValues(approval),
+ },
+ // itxn_field ApprovalProgram
+ {
+ Pc: 83,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 2
+ {
+ Pc: 85,
+ StackAdditions: goValuesToAvmValues(clearState),
+ },
+ // itxn_field ClearStateProgram
+ {
+ Pc: 87,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // itxn_submit
+ {
+ Pc: 89,
+ SpawnedInners: &[]uint64{0},
+ },
+ // itxn_begin
+ {
+ Pc: 90,
+ },
+ // int pay
+ {
+ Pc: 91,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // itxn_field TypeEnum
+ {
+ Pc: 92,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 0
+ {
+ Pc: 94,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // itxn_field Fee
+ {
+ Pc: 95,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 4
+ {
+ Pc: 97,
+ StackAdditions: goValuesToAvmValues(uint64(3-layer) * MinBalance),
+ },
+ // int 100000
+ {
+ Pc: 99,
+ StackAdditions: goValuesToAvmValues(MinBalance),
+ },
+ // -
+ {
+ Pc: 103,
+ StackPopCount: toPtr[uint64](2),
+ StackAdditions: goValuesToAvmValues(uint64(2-layer) * MinBalance),
+ },
+ // itxn_field Amount
+ {
+ Pc: 104,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // byte "appID"
+ {
+ Pc: 106,
+ StackAdditions: goValuesToAvmValues([]byte("appID")),
+ },
+ // gitxn 0 CreatedApplicationID
+ {
+ Pc: 113,
+ StackAdditions: goValuesToAvmValues(appID + 3),
+ },
+ // itob
+ {
+ Pc: 116,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(appID) + 3)),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // concat
+ {
+ Pc: 117,
+ StackAdditions: goValuesToAvmValues([]byte("appID" + string(uint64ToBytes(uint64(appID)+3)))),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // sha512_256
+ {
+ Pc: 118,
+ StackAdditions: goValuesToAvmValues(crypto.Digest(basics.AppIndex(uint64(appID) + 3).Address()).ToSlice()),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // itxn_field Receiver
+ {
+ Pc: 119,
+ StackPopCount: toPtr[uint64](1),
+ },
+ {
+ Pc: 121,
+ },
+ // int appl
+ {
+ Pc: 122,
+ StackAdditions: goValuesToAvmValues(6),
+ },
+ // itxn_field TypeEnum
+ {
+ Pc: 123,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // txna ApplicationArgs 0
+ {
+ Pc: 125,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
+ },
+ // btoi
+ {
+ Pc: 128,
+ StackAdditions: goValuesToAvmValues(MaxDepth - layer),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 1
+ {
+ Pc: 129,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // -
+ {
+ Pc: 130,
+ StackAdditions: goValuesToAvmValues(MaxDepth - layer - 1),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // itob
+ {
+ Pc: 131,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer - 1))),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // itxn_field ApplicationArgs
+ {
+ Pc: 132,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // itxn CreatedApplicationID
+ {
+ Pc: 134,
+ StackAdditions: goValuesToAvmValues(appID + 3),
+ },
+ // itxn_field ApplicationID
+ {
+ Pc: 136,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 0
+ {
+ Pc: 138,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // itxn_field Fee
+ {
+ Pc: 139,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int DeleteApplication
+ {
+ Pc: 141,
+ StackAdditions: goValuesToAvmValues(5),
+ },
+ // itxn_field OnCompletion
+ {
+ Pc: 143,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // itxn_submit
+ {
+ Pc: 145,
+ SpawnedInners: &[]uint64{1, 2},
+ },
+ // b main_l4
+ {
+ Pc: 146,
+ },
+ // int 1
+ {
+ Pc: 72,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // return
+ {
+ Pc: 73,
+ StackAdditions: goValuesToAvmValues(1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ }
+ }
+
+ finalDepthTrace := func(appID basics.AppIndex, layer int) *[]model.SimulationOpcodeTraceUnit {
+ return &[]model.SimulationOpcodeTraceUnit{
+ {
+ Pc: 1,
+ },
+ // txn ApplicationID
+ {
+ Pc: 6,
+ StackAdditions: goValuesToAvmValues(appID),
+ },
+ // int 0
+ {
+ Pc: 8,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // ==
+ {
+ Pc: 9,
+ StackAdditions: goValuesToAvmValues(false),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // bnz main_l6
+ {
+ Pc: 10,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // txn NumAppArgs
+ {
+ Pc: 13,
+ StackAdditions: goValuesToAvmValues(NumArgs),
+ },
+ // int 1
+ {
+ Pc: 15,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // ==
+ {
+ Pc: 16,
+ StackPopCount: toPtr[uint64](2),
+ StackAdditions: goValuesToAvmValues(true),
+ },
+ // bnz main_l3
+ {
+ Pc: 17,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // global CurrentApplicationID
+ {
+ Pc: 21,
+ StackAdditions: goValuesToAvmValues(appID),
+ },
+ // app_params_get AppApprovalProgram
+ {
+ Pc: 23,
+ StackAdditions: goValuesToAvmValues(approval, 1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 1
+ {
+ Pc: 25,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 0
+ {
+ Pc: 27,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // global CurrentApplicationID
+ {
+ Pc: 29,
+ StackAdditions: goValuesToAvmValues(appID),
+ },
+ // app_params_get AppClearStateProgram
+ {
+ Pc: 31,
+ StackAdditions: goValuesToAvmValues(clearState, 1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 3
+ {
+ Pc: 33,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 2
+ {
+ Pc: 35,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // global CurrentApplicationAddress
+ {
+ Pc: 37,
+ StackAdditions: goValuesToAvmValues(crypto.Digest(appID.Address()).ToSlice()),
+ },
+ // acct_params_get AcctBalance
+ {
+ Pc: 39,
+ StackAdditions: goValuesToAvmValues(uint64(3-layer)*MinBalance, 1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 5
+ {
+ Pc: 41,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // store 4
+ {
+ Pc: 43,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 1
+ {
+ Pc: 45,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // assert
+ {
+ Pc: 47,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 3
+ {
+ Pc: 48,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // assert
+ {
+ Pc: 50,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // load 5
+ {
+ Pc: 51,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // assert
+ {
+ Pc: 53,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 2
+ {
+ Pc: 54,
+ StackAdditions: goValuesToAvmValues(2),
+ },
+ // txna ApplicationArgs 0
+ {
+ Pc: 56,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
+ },
+ // btoi
+ {
+ Pc: 59,
+ StackAdditions: goValuesToAvmValues(uint64(MaxDepth - layer)),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // exp
+ {
+ Pc: 60,
+ StackAdditions: goValuesToAvmValues(1 << (MaxDepth - layer)),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // itob
+ {
+ Pc: 61,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(1 << uint64(MaxDepth-layer))),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // log
+ {
+ Pc: 62,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // txna ApplicationArgs 0
+ {
+ Pc: 63,
+ StackAdditions: goValuesToAvmValues(uint64ToBytes(uint64(MaxDepth - layer))),
+ },
+ // btoi
+ {
+ Pc: 66,
+ StackAdditions: goValuesToAvmValues(MaxDepth - layer),
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 0
+ {
+ Pc: 67,
+ StackAdditions: goValuesToAvmValues(0),
+ },
+ // >
+ {
+ Pc: 68,
+ StackAdditions: goValuesToAvmValues(MaxDepth-layer > 0),
+ StackPopCount: toPtr[uint64](2),
+ },
+ // bnz main_l5
+ {
+ Pc: 69,
+ StackPopCount: toPtr[uint64](1),
+ },
+ // int 1
+ {
+ Pc: 72,
+ StackAdditions: goValuesToAvmValues(1),
+ },
+ // return
+ {
+ Pc: 73,
+ StackAdditions: goValuesToAvmValues(1),
+ StackPopCount: toPtr[uint64](1),
+ },
+ }
+ }
+
+ a.Len(resp.TxnGroups[0].Txns, 2)
+ a.Nil(resp.TxnGroups[0].FailureMessage)
+ a.Nil(resp.TxnGroups[0].FailedAt)
+
+ a.Nil(resp.TxnGroups[0].Txns[0].TransactionTrace)
+
+ expectedTraceSecondTxn := &model.SimulationTransactionExecTrace{
+ ApprovalProgramTrace: recursiveLongOpcodeTrace(futureAppID, 0),
+ ApprovalProgramHash: toPtr(approvalHash.ToSlice()),
+ InnerTrace: &[]model.SimulationTransactionExecTrace{
+ {
+ ApprovalProgramTrace: &creationOpcodeTrace,
+ ApprovalProgramHash: toPtr(approvalHash.ToSlice()),
+ },
+ {},
+ {
+ ApprovalProgramTrace: recursiveLongOpcodeTrace(futureAppID+3, 1),
+ ApprovalProgramHash: toPtr(approvalHash.ToSlice()),
+ InnerTrace: &[]model.SimulationTransactionExecTrace{
+ {
+ ApprovalProgramTrace: &creationOpcodeTrace,
+ ApprovalProgramHash: toPtr(approvalHash.ToSlice()),
+ },
+ {},
+ {
+ ApprovalProgramTrace: finalDepthTrace(futureAppID+6, 2),
+ ApprovalProgramHash: toPtr(approvalHash.ToSlice()),
+ },
+ },
+ },
+ },
+ }
+ a.Equal(expectedTraceSecondTxn, resp.TxnGroups[0].Txns[1].TransactionTrace)
+
+ a.Equal(execTraceConfig, resp.ExecTraceConfig)
+}
+
+func TestSimulateScratchSlotChange(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json"))
+
+ // Get primary node
+ primaryNode, err := localFixture.GetNodeController("Primary")
+ a.NoError(err)
+
+ localFixture.Start()
+ defer primaryNode.FullStop()
+
+ // get lib goal client
+ testClient := localFixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode)
+
+ _, err = testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(senderAddress, "no addr with funds")
+ a.NoError(err)
+
+ ops, err := logic.AssembleString(
+ `#pragma version 8
+ global CurrentApplicationID
+ bz end
+ int 1
+ store 1
+ load 1
+ dup
+ stores
+ end:
+ int 1`)
+ a.NoError(err)
+ approval := ops.Program
+ approvalHash := crypto.Hash(approval)
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ MinFee := config.Consensus[protocol.ConsensusFuture].MinTxnFee
+ MinBalance := config.Consensus[protocol.ConsensusFuture].MinBalance
+
+ // create app and get the application ID
+ appCreateTxn, err := testClient.MakeUnsignedAppCreateTx(
+ transactions.NoOpOC, approval, clearState, gl,
+ lc, nil, nil, nil, nil, nil, 0)
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ submittedAppCreateTxn, err := helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+ futureAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+
+ // fund app account
+ _, err = testClient.SendPaymentFromWallet(
+ wh, nil, senderAddress, futureAppID.Address().String(),
+ 0, MinBalance, nil, "", 0, 0,
+ )
+ a.NoError(err)
+
+ // construct app calls
+ appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(futureAppID), [][]byte{}, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallTxn)
+ a.NoError(err)
+
+ appCallTxnSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn)
+ a.NoError(err)
+
+ // construct simulation request, with scratch slot change enabled
+ execTraceConfig := simulation.ExecTraceConfig{
+ Enable: true,
+ Scratch: true,
+ }
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {Txns: []transactions.SignedTxn{appCallTxnSigned}},
+ },
+ ExecTraceConfig: execTraceConfig,
+ }
+
+ // update the configuration file to enable EnableDeveloperAPI
+ err = primaryNode.FullStop()
+ a.NoError(err)
+ cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir())
+ a.NoError(err)
+ cfg.EnableDeveloperAPI = true
+ err = cfg.SaveToDisk(primaryNode.GetDataDir())
+ require.NoError(t, err)
+ localFixture.Start()
+
+ // simulate with wrong config (not enabled trace), see expected error
+ _, err = testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {Txns: []transactions.SignedTxn{appCallTxnSigned}},
+ },
+ ExecTraceConfig: simulation.ExecTraceConfig{Scratch: true},
+ })
+ a.ErrorContains(err, "basic trace must be enabled when enabling scratch slot change tracing")
+
+ // start real simulating
+ resp, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ // check if resp match expected result
+ a.Equal(execTraceConfig, resp.ExecTraceConfig)
+ a.Len(resp.TxnGroups[0].Txns, 1)
+ a.NotNil(resp.TxnGroups[0].Txns[0].TransactionTrace)
+
+ expectedTraceSecondTxn := &model.SimulationTransactionExecTrace{
+ ApprovalProgramTrace: &[]model.SimulationOpcodeTraceUnit{
+ {Pc: 1},
+ {Pc: 4},
+ {Pc: 6},
+ {Pc: 9},
+ {
+ Pc: 10,
+ ScratchChanges: &[]model.ScratchChange{
+ {
+ Slot: 1,
+ NewValue: model.AvmValue{
+ Type: 2,
+ Uint: toPtr[uint64](1),
+ },
+ },
+ },
+ },
+ {Pc: 12},
+ {Pc: 14},
+ {
+ Pc: 15,
+ ScratchChanges: &[]model.ScratchChange{
+ {
+ Slot: 1,
+ NewValue: model.AvmValue{
+ Type: 2,
+ Uint: toPtr[uint64](1),
+ },
+ },
+ },
+ },
+ {Pc: 16},
+ },
+ ApprovalProgramHash: toPtr(approvalHash.ToSlice()),
+ }
+ a.Equal(expectedTraceSecondTxn, resp.TxnGroups[0].Txns[0].TransactionTrace)
+}
+
+func TestSimulateExecTraceStateChange(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json"))
+
+ // Get primary node
+ primaryNode, err := localFixture.GetNodeController("Primary")
+ a.NoError(err)
+
+ localFixture.Start()
+ defer primaryNode.FullStop()
+
+ // get lib goal client
+ testClient := localFixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode)
+
+ _, err = testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(senderAddress, "no addr with funds")
+
+ addressDigest, err := basics.UnmarshalChecksumAddress(senderAddress)
+ a.NoError(err)
+
+ ops, err := logic.AssembleString(
+ `#pragma version 8
+txn ApplicationID
+bz end // Do nothing during create
+
+txn OnCompletion
+int OptIn
+==
+bnz end // Always allow optin
+
+byte "local"
+byte "global"
+txn ApplicationArgs 0
+match local global
+err // Unknown command
+
+local:
+ txn Sender
+ byte "local-int-key"
+ int 0xcafeb0ba
+ app_local_put
+ int 0
+ byte "local-bytes-key"
+ byte "xqcL"
+ app_local_put
+ b end
+
+global:
+ byte "global-int-key"
+ int 0xdeadbeef
+ app_global_put
+ byte "global-bytes-key"
+ byte "welt am draht"
+ app_global_put
+ b end
+
+end:
+ int 1`)
+ a.NoError(err)
+ approval := ops.Program
+ approvalHash := crypto.Hash(approval)
+
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{NumByteSlice: 1, NumUint: 1}
+ lc := basics.StateSchema{NumByteSlice: 1, NumUint: 1}
+
+ MinFee := config.Consensus[protocol.ConsensusFuture].MinTxnFee
+ MinBalance := config.Consensus[protocol.ConsensusFuture].MinBalance
+
+ // create app and get the application ID
+ appCreateTxn, err := testClient.MakeUnsignedAppCreateTx(
+ transactions.NoOpOC, approval, clearState, gl,
+ lc, nil, nil, nil, nil, nil, 0)
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ submittedAppCreateTxn, err := helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+ futureAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+
+ // fund app account
+ _, err = testClient.ConstructPayment(
+ senderAddress, futureAppID.Address().String(),
+ 0, MinBalance*2, nil, "", [32]byte{}, 0, 0,
+ )
+ a.NoError(err)
+
+ // construct app call "global"
+ appCallGlobalTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(futureAppID), [][]byte{[]byte("global")}, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallGlobalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallGlobalTxn)
+ a.NoError(err)
+ // construct app optin
+ appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(uint64(futureAppID), nil, nil, nil, nil, nil)
+ a.NoError(err)
+ appOptInTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appOptInTxn)
+ // construct app call "global"
+ appCallLocalTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(futureAppID), [][]byte{[]byte("local")}, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallLocalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallLocalTxn)
+ a.NoError(err)
+
+ gid, err := testClient.GroupID([]transactions.Transaction{appCallGlobalTxn, appOptInTxn, appCallLocalTxn})
+ a.NoError(err)
+ appCallGlobalTxn.Group = gid
+ appOptInTxn.Group = gid
+ appCallLocalTxn.Group = gid
+
+ appCallTxnGlobalSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallGlobalTxn)
+ a.NoError(err)
+ appOptInSigned, err := testClient.SignTransactionWithWallet(wh, nil, appOptInTxn)
+ a.NoError(err)
+ appCallTxnLocalSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallLocalTxn)
+ a.NoError(err)
+
+ // construct simulation request, with state change enabled
+ execTraceConfig := simulation.ExecTraceConfig{
+ Enable: true,
+ State: true,
+ }
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {Txns: []transactions.SignedTxn{appCallTxnGlobalSigned, appOptInSigned, appCallTxnLocalSigned}},
+ },
+ ExecTraceConfig: execTraceConfig,
+ }
+
+ // update the configuration file to enable EnableDeveloperAPI
+ err = primaryNode.FullStop()
+ a.NoError(err)
+ cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir())
+ a.NoError(err)
+ cfg.EnableDeveloperAPI = true
+ err = cfg.SaveToDisk(primaryNode.GetDataDir())
+ require.NoError(t, err)
+ localFixture.Start()
+
+ // start real simulating
+ resp, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ // assertions
+ a.Len(resp.TxnGroups, 1)
+ a.Nil(resp.TxnGroups[0].FailureMessage)
+ a.Len(resp.TxnGroups[0].Txns, 3)
+
+ for i := 0; i < 3; i++ {
+ a.NotNil(resp.TxnGroups[0].Txns[i].TransactionTrace.ApprovalProgramHash)
+ a.Equal(approvalHash.ToSlice(), *resp.TxnGroups[0].Txns[i].TransactionTrace.ApprovalProgramHash)
+ }
+
+ a.Equal([]model.SimulationOpcodeTraceUnit{
+ {Pc: 1},
+ {Pc: 4},
+ {Pc: 6},
+ {Pc: 9},
+ {Pc: 11},
+ {Pc: 12},
+ {Pc: 13},
+ {Pc: 16},
+ {Pc: 23},
+ {Pc: 31},
+ {Pc: 34},
+ {Pc: 94},
+ {Pc: 110},
+ {
+ Pc: 116,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "g",
+ Key: []byte("global-int-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: toPtr[uint64](0xdeadbeef),
+ },
+ },
+ },
+ },
+ {Pc: 117},
+ {Pc: 135},
+ {
+ Pc: 150,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "g",
+ Key: []byte("global-bytes-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealBytesType),
+ Bytes: toPtr([]byte("welt am draht")),
+ },
+ },
+ },
+ },
+ {Pc: 151},
+ {Pc: 154},
+ }, *resp.TxnGroups[0].Txns[0].TransactionTrace.ApprovalProgramTrace)
+ a.NotNil(resp.TxnGroups[0].Txns[1].TransactionTrace.ApprovalProgramHash)
+ a.Equal([]model.SimulationOpcodeTraceUnit{
+ {Pc: 1},
+ {Pc: 4},
+ {Pc: 6},
+ {Pc: 9},
+ {Pc: 11},
+ {Pc: 12},
+ {Pc: 13},
+ {Pc: 154},
+ }, *resp.TxnGroups[0].Txns[1].TransactionTrace.ApprovalProgramTrace)
+ a.Equal([]model.SimulationOpcodeTraceUnit{
+ {Pc: 1},
+ {Pc: 4},
+ {Pc: 6},
+ {Pc: 9},
+ {Pc: 11},
+ {Pc: 12},
+ {Pc: 13},
+ {Pc: 16},
+ {Pc: 23},
+ {Pc: 31},
+ {Pc: 34},
+ {Pc: 41},
+ {Pc: 43},
+ {Pc: 58},
+ {
+ Pc: 64,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "l",
+ Key: []byte("local-int-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: toPtr[uint64](0xcafeb0ba),
+ },
+ Account: toPtr(addressDigest.String()),
+ },
+ },
+ },
+ {Pc: 65},
+ {Pc: 67},
+ {Pc: 84},
+ {
+ Pc: 90,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "l",
+ Key: []byte("local-bytes-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealBytesType),
+ Bytes: toPtr([]byte("xqcL")),
+ },
+ Account: toPtr(addressDigest.String()),
+ },
+ },
+ },
+ {Pc: 91},
+ {Pc: 154},
+ }, *resp.TxnGroups[0].Txns[2].TransactionTrace.ApprovalProgramTrace)
+}
+
+func TestSimulateExecTraceAppInitialState(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json"))
+
+ // Get primary node
+ primaryNode, err := localFixture.GetNodeController("Primary")
+ a.NoError(err)
+
+ localFixture.Start()
+ defer primaryNode.FullStop()
+
+ // get lib goal client
+ testClient := localFixture.LibGoalFixture.GetLibGoalClientFromNodeController(primaryNode)
+
+ _, err = testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(senderAddress, "no addr with funds")
+
+ addressDigest, err := basics.UnmarshalChecksumAddress(senderAddress)
+ a.NoError(err)
+
+ ops, err := logic.AssembleString(
+ `#pragma version 8
+txn ApplicationID
+bz end // Do nothing during create
+
+txn OnCompletion
+int OptIn
+==
+bnz end // Always allow optin
+
+byte "local"
+byte "global"
+txn ApplicationArgs 0
+match local global
+err // Unknown command
+
+local:
+ txn Sender
+ byte "local-int-key"
+ int 0xcafeb0ba
+ app_local_put
+ int 0
+ byte "local-bytes-key"
+ byte "xqcL"
+ app_local_put
+ b end
+
+global:
+ byte "global-int-key"
+ int 0xdeadbeef
+ app_global_put
+ byte "global-bytes-key"
+ byte "welt am draht"
+ app_global_put
+ b end
+
+end:
+ int 1`)
+ a.NoError(err)
+ approval := ops.Program
+
+ ops, err = logic.AssembleString("#pragma version 8\nint 1")
+ a.NoError(err)
+ clearState := ops.Program
+
+ gl := basics.StateSchema{NumByteSlice: 1, NumUint: 1}
+ lc := basics.StateSchema{NumByteSlice: 1, NumUint: 1}
+
+ MinFee := config.Consensus[protocol.ConsensusFuture].MinTxnFee
+ MinBalance := config.Consensus[protocol.ConsensusFuture].MinBalance
+
+ // create app and get the application ID
+ appCreateTxn, err := testClient.MakeUnsignedAppCreateTx(
+ transactions.NoOpOC, approval, clearState, gl,
+ lc, nil, nil, nil, nil, nil, 0)
+ a.NoError(err)
+ appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
+ a.NoError(err)
+
+ appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn)
+ a.NoError(err)
+ submittedAppCreateTxn, err := helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second)
+ a.NoError(err)
+ futureAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex)
+
+ // fund app account
+ _, err = testClient.ConstructPayment(
+ senderAddress, futureAppID.Address().String(),
+ 0, MinBalance*2, nil, "", [32]byte{}, 0, 0,
+ )
+ a.NoError(err)
+
+ // construct app call "global"
+ appCallGlobalTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(futureAppID), [][]byte{[]byte("global")}, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallGlobalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallGlobalTxn)
+ a.NoError(err)
+ // construct app optin
+ appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(uint64(futureAppID), nil, nil, nil, nil, nil)
+ a.NoError(err)
+ appOptInTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appOptInTxn)
+ // construct app call "local"
+ appCallLocalTxn, err := testClient.MakeUnsignedAppNoOpTx(
+ uint64(futureAppID), [][]byte{[]byte("local")}, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ appCallLocalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallLocalTxn)
+ a.NoError(err)
+
+ gid, err := testClient.GroupID([]transactions.Transaction{appCallGlobalTxn, appOptInTxn, appCallLocalTxn})
+ a.NoError(err)
+ appCallGlobalTxn.Group = gid
+ appOptInTxn.Group = gid
+ appCallLocalTxn.Group = gid
+
+ appCallTxnGlobalSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallGlobalTxn)
+ a.NoError(err)
+ appOptInSigned, err := testClient.SignTransactionWithWallet(wh, nil, appOptInTxn)
+ a.NoError(err)
+ appCallTxnLocalSigned, err := testClient.SignTransactionWithWallet(wh, nil, appCallLocalTxn)
+ a.NoError(err)
+
+ a.NoError(testClient.BroadcastTransactionGroup([]transactions.SignedTxn{
+ appCallTxnGlobalSigned,
+ appOptInSigned,
+ appCallTxnLocalSigned,
+ }))
+ _, err = helper.WaitForTransaction(t, testClient, appCallTxnGlobalSigned.Txn.ID().String(), 30*time.Second)
+ a.NoError(err)
+
+ // construct simulation request, with state change enabled
+ execTraceConfig := simulation.ExecTraceConfig{
+ Enable: true,
+ State: true,
+ }
+
+ appCallGlobalTxn.Note = []byte("note for global")
+ appCallGlobalTxn.Group = crypto.Digest{}
+ appCallLocalTxn.Note = []byte("note for local")
+ appCallLocalTxn.Group = crypto.Digest{}
+
+ gid, err = testClient.GroupID([]transactions.Transaction{appCallGlobalTxn, appCallLocalTxn})
+ a.NoError(err)
+ appCallGlobalTxn.Group = gid
+ appCallLocalTxn.Group = gid
+
+ appCallTxnGlobalSigned, err = testClient.SignTransactionWithWallet(wh, nil, appCallGlobalTxn)
+ a.NoError(err)
+ appCallTxnLocalSigned, err = testClient.SignTransactionWithWallet(wh, nil, appCallLocalTxn)
+ a.NoError(err)
+
+ simulateRequest := v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {Txns: []transactions.SignedTxn{appCallTxnGlobalSigned, appCallTxnLocalSigned}},
+ },
+ ExecTraceConfig: execTraceConfig,
+ }
+
+ // update the configuration file to enable EnableDeveloperAPI
+ err = primaryNode.FullStop()
+ a.NoError(err)
+ cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir())
+ a.NoError(err)
+ cfg.EnableDeveloperAPI = true
+ err = cfg.SaveToDisk(primaryNode.GetDataDir())
+ require.NoError(t, err)
+ localFixture.Start()
+
+ // start real simulating
+ resp, err := testClient.SimulateTransactions(simulateRequest)
+ a.NoError(err)
+
+ // assertions
+ a.Len(resp.TxnGroups, 1)
+ a.Nil(resp.TxnGroups[0].FailureMessage)
+ a.Len(resp.TxnGroups[0].Txns, 2)
+
+ a.Equal([]model.SimulationOpcodeTraceUnit{
+ {Pc: 1},
+ {Pc: 4},
+ {Pc: 6},
+ {Pc: 9},
+ {Pc: 11},
+ {Pc: 12},
+ {Pc: 13},
+ {Pc: 16},
+ {Pc: 23},
+ {Pc: 31},
+ {Pc: 34},
+ {Pc: 94},
+ {Pc: 110},
+ {
+ Pc: 116,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "g",
+ Key: []byte("global-int-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: toPtr[uint64](0xdeadbeef),
+ },
+ },
+ },
+ },
+ {Pc: 117},
+ {Pc: 135},
+ {
+ Pc: 150,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "g",
+ Key: []byte("global-bytes-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealBytesType),
+ Bytes: toPtr([]byte("welt am draht")),
+ },
+ },
+ },
+ },
+ {Pc: 151},
+ {Pc: 154},
+ }, *resp.TxnGroups[0].Txns[0].TransactionTrace.ApprovalProgramTrace)
+ a.Equal([]model.SimulationOpcodeTraceUnit{
+ {Pc: 1},
+ {Pc: 4},
+ {Pc: 6},
+ {Pc: 9},
+ {Pc: 11},
+ {Pc: 12},
+ {Pc: 13},
+ {Pc: 16},
+ {Pc: 23},
+ {Pc: 31},
+ {Pc: 34},
+ {Pc: 41},
+ {Pc: 43},
+ {Pc: 58},
+ {
+ Pc: 64,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "l",
+ Key: []byte("local-int-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealUintType),
+ Uint: toPtr[uint64](0xcafeb0ba),
+ },
+ Account: toPtr(addressDigest.String()),
+ },
+ },
+ },
+ {Pc: 65},
+ {Pc: 67},
+ {Pc: 84},
+ {
+ Pc: 90,
+ StateChanges: &[]model.ApplicationStateOperation{
+ {
+ Operation: "w",
+ AppStateType: "l",
+ Key: []byte("local-bytes-key"),
+ NewValue: &model.AvmValue{
+ Type: uint64(basics.TealBytesType),
+ Bytes: toPtr([]byte("xqcL")),
+ },
+ Account: toPtr(addressDigest.String()),
+ },
+ },
+ },
+ {Pc: 91},
+ {Pc: 154},
+ }, *resp.TxnGroups[0].Txns[1].TransactionTrace.ApprovalProgramTrace)
+
+ a.NotNil(resp.InitialStates)
+ a.Len(*resp.InitialStates.AppInitialStates, 1)
+
+ a.Len((*resp.InitialStates.AppInitialStates)[0].AppGlobals.Kvs, 2)
+
+ globalKVs := (*resp.InitialStates.AppInitialStates)[0].AppGlobals.Kvs
+ globalKVMap := make(map[string]model.AvmValue)
+ for _, kv := range globalKVs {
+ globalKVMap[string(kv.Key)] = kv.Value
+ }
+ expectedGlobalKVMap := map[string]model.AvmValue{
+ "global-int-key": {
+ Type: 2,
+ Uint: toPtr[uint64](0xdeadbeef),
+ },
+ "global-bytes-key": {
+ Type: 1,
+ Bytes: toPtr([]byte("welt am draht")),
+ },
+ }
+ a.Equal(expectedGlobalKVMap, globalKVMap)
+
+ a.Len(*(*resp.InitialStates.AppInitialStates)[0].AppLocals, 1)
+
+ localKVs := (*(*resp.InitialStates.AppInitialStates)[0].AppLocals)[0]
+ a.NotNil(localKVs.Account)
+ a.Equal(senderAddress, *localKVs.Account)
+
+ localKVMap := make(map[string]model.AvmValue)
+ for _, kv := range localKVs.Kvs {
+ localKVMap[string(kv.Key)] = kv.Value
+ }
+ expectedLocalKVMap := map[string]model.AvmValue{
+ "local-int-key": {
+ Type: 2,
+ Uint: toPtr[uint64](0xcafeb0ba),
+ },
+ "local-bytes-key": {
+ Type: 1,
+ Bytes: toPtr([]byte("xqcL")),
+ },
+ }
+ a.Equal(expectedLocalKVMap, localKVMap)
+}
+
+func TestSimulateWithUnnamedResources(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+
+ _, err := testClient.WaitForRound(1)
+ a.NoError(err)
+
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, senderAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(senderAddress, "no addr with funds")
+ a.NoError(err)
+
+ otherAddress := helper.GetDestAddr(t, testClient, nil, senderAddress, wh)
+
+ // fund otherAddress
+ txn, err := testClient.SendPaymentFromWallet(
+ wh, nil, senderAddress, otherAddress,
+ 0, 1_000_000, nil, "", 0, 0,
+ )
+ a.NoError(err)
+ txID := txn.ID().String()
+ _, err = helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+
+ // create asset
+ txn, err = testClient.MakeUnsignedAssetCreateTx(100, false, "", "", "", "", "", "", "", nil, 0)
+ a.NoError(err)
+ txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
+ a.NoError(err)
+ // sign and broadcast
+ txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
+ a.NoError(err)
+ confirmedTxn, err := helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+ // get asset ID
+ a.NotNil(confirmedTxn.AssetIndex)
+ assetID := *confirmedTxn.AssetIndex
+ a.NotZero(assetID)
+
+ // opt-in to asset
+ txn, err = testClient.MakeUnsignedAssetSendTx(assetID, 0, otherAddress, "", "")
+ a.NoError(err)
+ txn, err = testClient.FillUnsignedTxTemplate(otherAddress, 0, 0, 0, txn)
+ a.NoError(err)
+ // sign and broadcast
+ txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+
+ // transfer asset
+ txn, err = testClient.MakeUnsignedAssetSendTx(assetID, 1, otherAddress, "", "")
+ a.NoError(err)
+ txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
+ a.NoError(err)
+ // sign and broadcast
+ txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+
+ ops, err := logic.AssembleString("#pragma version 9\n int 1")
+ a.NoError(err)
+ alwaysApprove := ops.Program
+
+ gl := basics.StateSchema{}
+ lc := basics.StateSchema{}
+
+ // create app
+ txn, err = testClient.MakeUnsignedAppCreateTx(transactions.OptInOC, alwaysApprove, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0)
+ a.NoError(err)
+ txn, err = testClient.FillUnsignedTxTemplate(otherAddress, 0, 0, 0, txn)
+ a.NoError(err)
+ // sign and broadcast
+ txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
+ a.NoError(err)
+ confirmedTxn, err = helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+ // get app ID
+ a.NotNil(confirmedTxn.ApplicationIndex)
+ otherAppID := basics.AppIndex(*confirmedTxn.ApplicationIndex)
+ a.NotZero(otherAppID)
+
+ prog := fmt.Sprintf(`#pragma version 9
+txn ApplicationID
+bz end
+
+addr %s // otherAddress
+store 0
+
+int %d // assetID
+store 1
+
+int %d // otherAppID
+store 2
+
+// Account access
+load 0 // otherAddress
+balance
+assert
+
+// Asset params access
+load 1 // assetID
+asset_params_get AssetTotal
+assert
+int 100
+==
+assert
+
+// Asset holding access
+load 0 // otherAddress
+load 1 // assetID
+asset_holding_get AssetBalance
+assert
+int 1
+==
+assert
+
+// App params access
+load 2 // otherAppID
+app_params_get AppCreator
+assert
+load 0 // otherAddress
+==
+assert
+
+// App local access
+load 0 // otherAddress
+load 2 // otherAppID
+app_opted_in
+assert
+
+// Box access
+byte "A"
+int 1025
+box_create
+assert
+
+end:
+int 1
+`, otherAddress, assetID, otherAppID)
+
+ ops, err = logic.AssembleString(prog)
+ a.NoError(err)
+ approval := ops.Program
+
+ // create app
+ txn, err = testClient.MakeUnsignedAppCreateTx(transactions.NoOpOC, approval, alwaysApprove, gl, lc, nil, nil, nil, nil, nil, 0)
+ a.NoError(err)
+ txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
+ a.NoError(err)
+ // sign and broadcast
+ txID, err = testClient.SignAndBroadcastTransaction(wh, nil, txn)
+ a.NoError(err)
+ confirmedTxn, err = helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+ // get app ID
+ a.NotNil(confirmedTxn.ApplicationIndex)
+ testAppID := basics.AppIndex(*confirmedTxn.ApplicationIndex)
+ a.NotZero(testAppID)
+
+ // fund app account
+ txn, err = testClient.SendPaymentFromWallet(
+ wh, nil, senderAddress, testAppID.Address().String(),
+ 0, 1_000_000, nil, "", 0, 0,
+ )
+ a.NoError(err)
+ txID = txn.ID().String()
+ _, err = helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+
+ // construct app call
+ txn, err = testClient.MakeUnsignedAppNoOpTx(
+ uint64(testAppID), nil, nil, nil, nil, nil,
+ )
+ a.NoError(err)
+ txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
+ a.NoError(err)
+ stxn, err := testClient.SignTransactionWithWallet(wh, nil, txn)
+ a.NoError(err)
+
+ // Cannot access these resources by default
+ resp, err := testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{stxn},
+ },
+ },
+ AllowUnnamedResources: false,
+ })
+ a.NoError(err)
+ a.Contains(*resp.TxnGroups[0].FailureMessage, "logic eval error: invalid Account reference "+otherAddress)
+ a.Equal([]uint64{0}, *resp.TxnGroups[0].FailedAt)
+
+ // It should work with AllowUnnamedResources=true
+ resp, err = testClient.SimulateTransactions(v2.PreEncodedSimulateRequest{
+ TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{
+ {
+ Txns: []transactions.SignedTxn{stxn},
+ },
+ },
+ AllowUnnamedResources: true,
+ })
+ a.NoError(err)
+
+ expectedUnnamedGroupResources := model.SimulateUnnamedResourcesAccessed{
+ Accounts: &[]string{otherAddress},
+ Assets: &[]uint64{assetID},
+ Apps: &[]uint64{uint64(otherAppID)},
+ Boxes: &[]model.BoxReference{{App: uint64(testAppID), Name: []byte("A")}},
+ ExtraBoxRefs: toPtr[uint64](1),
+ AssetHoldings: &[]model.AssetHoldingReference{
+ {Account: otherAddress, Asset: assetID},
+ },
+ AppLocals: &[]model.ApplicationLocalReference{
+ {Account: otherAddress, App: uint64(otherAppID)},
+ },
+ }
+
+ budgetAdded, budgetUsed := uint64(700), uint64(40)
+ allowUnnamedResources := true
+
+ expectedResult := v2.PreEncodedSimulateResponse{
+ Version: 2,
+ LastRound: resp.LastRound,
+ EvalOverrides: &model.SimulationEvalOverrides{
+ AllowUnnamedResources: &allowUnnamedResources,
+ },
+ TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{
+ {
+ Txns: []v2.PreEncodedSimulateTxnResult{
+ {
+ Txn: v2.PreEncodedTxInfo{Txn: stxn},
+ AppBudgetConsumed: &budgetUsed,
+ },
+ },
+ AppBudgetAdded: &budgetAdded,
+ AppBudgetConsumed: &budgetUsed,
+ UnnamedResourcesAccessed: &expectedUnnamedGroupResources,
+ },
+ },
+ }
+ a.Equal(expectedResult, resp)
+}
diff --git a/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go b/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go
new file mode 100644
index 0000000000..497d128643
--- /dev/null
+++ b/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go
@@ -0,0 +1,206 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package stateproof
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/crypto/merklesignature"
+ "github.com/algorand/go-algorand/data/account"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/framework/fixtures"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/algorand/go-algorand/util/db"
+ "github.com/stretchr/testify/require"
+
+ helper "github.com/algorand/go-algorand/test/e2e-go/restAPI"
+)
+
+func TestStateProofInParticipationInfo(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+
+ proto := config.Consensus[protocol.ConsensusCurrentVersion]
+ localFixture.SetConsensus(config.ConsensusProtocols{protocol.ConsensusCurrentVersion: proto})
+
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+ helper.WaitForRoundOne(t, testClient)
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, someAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(someAddress, "no addr with funds")
+
+ addr, err := basics.UnmarshalChecksumAddress(someAddress)
+ a.NoError(err)
+
+ params, err := testClient.SuggestedParams()
+ a.NoError(err)
+
+ firstRound := basics.Round(params.LastRound + 1)
+ lastRound := basics.Round(params.LastRound + 1000)
+ dilution := uint64(100)
+ randomVotePKStr := helper.RandomString(32)
+ var votePK crypto.OneTimeSignatureVerifier
+ copy(votePK[:], randomVotePKStr)
+ randomSelPKStr := helper.RandomString(32)
+ var selPK crypto.VRFVerifier
+ copy(selPK[:], randomSelPKStr)
+ var mssRoot [merklesignature.MerkleSignatureSchemeRootSize]byte
+ randomRootStr := helper.RandomString(merklesignature.MerkleSignatureSchemeRootSize)
+ copy(mssRoot[:], randomRootStr)
+ var gh crypto.Digest
+ copy(gh[:], params.GenesisHash)
+
+ tx := transactions.Transaction{
+ Type: protocol.KeyRegistrationTx,
+ Header: transactions.Header{
+ Sender: addr,
+ Fee: basics.MicroAlgos{Raw: 10000},
+ FirstValid: firstRound,
+ LastValid: lastRound,
+ GenesisHash: gh,
+ },
+ KeyregTxnFields: transactions.KeyregTxnFields{
+ VotePK: votePK,
+ SelectionPK: selPK,
+ VoteFirst: firstRound,
+ StateProofPK: mssRoot,
+ VoteLast: lastRound,
+ VoteKeyDilution: dilution,
+ Nonparticipation: false,
+ },
+ }
+ txID, err := testClient.SignAndBroadcastTransaction(wh, nil, tx)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, txID, 120*time.Second)
+ a.NoError(err)
+
+ account, err := testClient.AccountInformation(someAddress, false)
+ a.NoError(err)
+ a.NotNil(account.Participation.StateProofKey)
+
+ actual := [merklesignature.MerkleSignatureSchemeRootSize]byte{}
+ copy(actual[:], *account.Participation.StateProofKey)
+ a.Equal(mssRoot, actual)
+}
+
+func TestStateProofParticipationKeysAPI(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50Each.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+ helper.WaitForRoundOne(t, testClient)
+
+ partdb, err := db.MakeErasableAccessor(filepath.Join(testClient.DataDir(), "/..", "/Wallet1.0.3000.partkey"))
+ a.NoError(err)
+
+ partkey, err := account.RestoreParticipation(partdb)
+ a.NoError(err)
+
+ pRoot, err := testClient.GetParticipationKeys()
+ a.NoError(err)
+
+ actual := [merklesignature.MerkleSignatureSchemeRootSize]byte{}
+ a.NotNil(pRoot[0].Key.StateProofKey)
+ copy(actual[:], *pRoot[0].Key.StateProofKey)
+ a.Equal(partkey.StateProofSecrets.GetVerifier().Commitment[:], actual[:])
+}
+
+func TestNilStateProofInParticipationInfo(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ defer fixtures.ShutdownSynchronizedTest(t)
+
+ a := require.New(fixtures.SynchronizedTest(t))
+ var localFixture fixtures.RestClientFixture
+
+ localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachV30.json"))
+ defer localFixture.Shutdown()
+
+ testClient := localFixture.LibGoalClient
+ helper.WaitForRoundOne(t, testClient)
+ wh, err := testClient.GetUnencryptedWalletHandle()
+ a.NoError(err)
+ addresses, err := testClient.ListAddresses(wh)
+ a.NoError(err)
+ _, someAddress := helper.GetMaxBalAddr(t, testClient, addresses)
+ a.NotEmpty(someAddress, "no addr with funds")
+
+ addr, err := basics.UnmarshalChecksumAddress(someAddress)
+ a.NoError(err)
+
+ params, err := testClient.SuggestedParams()
+ a.NoError(err)
+
+ firstRound := basics.Round(1)
+ lastRound := basics.Round(20)
+ dilution := uint64(100)
+ randomVotePKStr := helper.RandomString(32)
+ var votePK crypto.OneTimeSignatureVerifier
+ copy(votePK[:], []byte(randomVotePKStr))
+ randomSelPKStr := helper.RandomString(32)
+ var selPK crypto.VRFVerifier
+ copy(selPK[:], []byte(randomSelPKStr))
+ var gh crypto.Digest
+ copy(gh[:], params.GenesisHash)
+
+ tx := transactions.Transaction{
+ Type: protocol.KeyRegistrationTx,
+ Header: transactions.Header{
+ Sender: addr,
+ Fee: basics.MicroAlgos{Raw: 10000},
+ FirstValid: firstRound,
+ LastValid: lastRound,
+ GenesisHash: gh,
+ },
+ KeyregTxnFields: transactions.KeyregTxnFields{
+ VotePK: votePK,
+ SelectionPK: selPK,
+ VoteFirst: firstRound,
+ VoteLast: lastRound,
+ VoteKeyDilution: dilution,
+ Nonparticipation: false,
+ },
+ }
+ txID, err := testClient.SignAndBroadcastTransaction(wh, nil, tx)
+ a.NoError(err)
+ _, err = helper.WaitForTransaction(t, testClient, txID, 30*time.Second)
+ a.NoError(err)
+
+ account, err := testClient.AccountInformation(someAddress, false)
+ a.NoError(err)
+ a.Nil(account.Participation.StateProofKey)
+}
diff --git a/test/framework/fixtures/fixture.go b/test/framework/fixtures/fixture.go
index 83bb7d7120..4ccba1accf 100644
--- a/test/framework/fixtures/fixture.go
+++ b/test/framework/fixtures/fixture.go
@@ -161,11 +161,13 @@ func (st *synchTest) Helper() {
st.t.Helper()
}
func (st *synchTest) Log(args ...interface{}) {
+ st.t.Helper()
st.Lock()
defer st.Unlock()
st.t.Log(args...)
}
func (st *synchTest) Logf(format string, args ...interface{}) {
+ st.t.Helper()
st.Lock()
defer st.Unlock()
st.t.Logf(format, args...)
diff --git a/test/framework/fixtures/goalFixture.go b/test/framework/fixtures/goalFixture.go
index ef52b51b63..69e43e784a 100644
--- a/test/framework/fixtures/goalFixture.go
+++ b/test/framework/fixtures/goalFixture.go
@@ -58,19 +58,27 @@ const (
nodeCmd = "node"
startCmd = "start"
stopCmd = "stop"
+
+ networkCmd = "network"
+ pregenCmd = "pregen"
+ createCmd = "create"
)
-func (f *GoalFixture) executeCommand(args ...string) (retStdout string, retStderr string, err error) {
+func (f *GoalFixture) executeRawCommand(args ...string) (retStdout string, retStderr string, err error) {
+ // Executes a command without a specified data directory
cmd := filepath.Join(f.binDir, goalCmd)
- // We always execute goal against the PrimaryDataDir() instance
- args = append(args, "-d", f.PrimaryDataDir())
retStdout, retStderr, err = util.ExecAndCaptureOutput(cmd, args...)
retStdout = strings.TrimRight(retStdout, "\n")
retStderr = strings.TrimRight(retStderr, "\n")
- //fmt.Printf("command: %v %v\nret: %v\n", cmd, args, ret)
return
}
+func (f *GoalFixture) executeCommand(args ...string) (retStdout string, retStderr string, err error) {
+ // We always execute goal against the PrimaryDataDir() instance
+ args = append(args, "-d", f.PrimaryDataDir())
+ return f.executeRawCommand(args...)
+}
+
// combine the error and the output so that we could return it as a single error object.
func combineExecuteError(retStdout string, retStderr string, err error) error {
if err == nil {
@@ -227,3 +235,63 @@ func (f *GoalFixture) AccountImportRootKey(wallet string, createDefaultUnencrypt
_, _, err = f.executeCommand(args...)
return
}
+
+// NetworkPregen exposes the `goal network pregen` command
+func (f *GoalFixture) NetworkPregen(template, pregendir string) (stdErr string, err error) {
+ args := []string{
+ networkCmd,
+ pregenCmd,
+ "-p",
+ pregendir,
+ }
+ if template != "" {
+ args = append(args, "-t", template)
+ }
+ _, stdErr, err = f.executeRawCommand(args...)
+ return
+}
+
+// NetworkCreate exposes the `goal network create` command
+func (f *GoalFixture) NetworkCreate(networkdir, networkName, template, pregendir string) (err error) {
+ args := []string{
+ networkCmd,
+ createCmd,
+ "-r",
+ networkdir,
+ }
+ if networkName != "" {
+ args = append(args, "-n", networkName)
+ }
+ if template != "" {
+ args = append(args, "-t", template)
+ }
+ if pregendir != "" {
+ args = append(args, "-p", pregendir)
+ }
+ _, _, err = f.executeRawCommand(args...)
+ return
+}
+
+// NetworkStart exposes the `goal network start` command
+func (f *GoalFixture) NetworkStart(networkdir string) (err error) {
+ args := []string{
+ networkCmd,
+ startCmd,
+ "-r",
+ networkdir,
+ }
+ _, _, err = f.executeRawCommand(args...)
+ return
+}
+
+// NetworkStop exposes the `goal network stop` command
+func (f *GoalFixture) NetworkStop(networkdir string) (err error) {
+ args := []string{
+ networkCmd,
+ stopCmd,
+ "-r",
+ networkdir,
+ }
+ _, _, err = f.executeRawCommand(args...)
+ return
+}
diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go
index c0527fdd62..1cc0b24fb8 100644
--- a/test/framework/fixtures/libgoalFixture.go
+++ b/test/framework/fixtures/libgoalFixture.go
@@ -534,7 +534,7 @@ func (f *LibGoalFixture) TransactionProof(txid string, round uint64, hashType cr
return model.TransactionProofResponse{}, merklearray.SingleLeafProof{}, err
}
- proof, err := merklearray.ProofDataToSingleLeafProof(string(proofResp.Hashtype), proofResp.Treedepth, proofResp.Proof)
+ proof, err := merklearray.ProofDataToSingleLeafProof(string(proofResp.Hashtype), proofResp.Proof)
if err != nil {
return model.TransactionProofResponse{}, merklearray.SingleLeafProof{}, err
}
@@ -550,7 +550,7 @@ func (f *LibGoalFixture) LightBlockHeaderProof(round uint64) (model.LightBlockHe
return model.LightBlockHeaderProofResponse{}, merklearray.SingleLeafProof{}, err
}
- proof, err := merklearray.ProofDataToSingleLeafProof(crypto.Sha256.String(), proofResp.Treedepth, proofResp.Proof)
+ proof, err := merklearray.ProofDataToSingleLeafProof(crypto.Sha256.String(), proofResp.Proof)
if err != nil {
return model.LightBlockHeaderProofResponse{}, merklearray.SingleLeafProof{}, err
}
diff --git a/test/heapwatch/.gitignore b/test/heapwatch/.gitignore
new file mode 100644
index 0000000000..c20c2ab731
--- /dev/null
+++ b/test/heapwatch/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+
diff --git a/test/heapwatch/client_ram_report.py b/test/heapwatch/client_ram_report.py
index 29642faf15..7833ababa3 100644
--- a/test/heapwatch/client_ram_report.py
+++ b/test/heapwatch/client_ram_report.py
@@ -181,8 +181,9 @@ def hostports_to_nicks(args, hostports, metrics=None):
if not hit:
hit = hp
out.append(hit)
+ out.sort()
if metrics:
- return ['{}#{}'.format(hp, m) for hp in hostports for m in metrics]
+ return ['{}#{}'.format(hp, m) for hp in out for m in metrics]
return out
diff --git a/test/heapwatch/metrics_delta.py b/test/heapwatch/metrics_delta.py
index d50346ec04..420130df91 100644
--- a/test/heapwatch/metrics_delta.py
+++ b/test/heapwatch/metrics_delta.py
@@ -371,6 +371,21 @@ def process_nick_re(nre, filesByNick, nick_to_tfname, rsum, args, grsum):
'npn': (.7,.7,0),
}
+def terraform_inventory_ip_not_names(tf_inventory_path):
+ """return ip to nickname mapping"""
+ tf_inventory = configparser.ConfigParser(allow_no_value=True)
+ tf_inventory.read(tf_inventory_path)
+ ip_to_name = {}
+ for k, sub in tf_inventory.items():
+ if k.startswith('name_'):
+ for ip in sub:
+ if ip in ip_to_name:
+ logger.warning('ip %r already named %r, also got %r', ip, ip_to_name[ip], k)
+ ip_to_name[ip] = k
+ #logger.debug('names: %r', sorted(ip_to_name.values()))
+ #logger.debug('ip to name %r', ip_to_name)
+ return ip_to_name
+
def main():
os.environ['TZ'] = 'UTC'
time.tzset()
@@ -409,17 +424,7 @@ def main():
break
nick_to_tfname = {}
if tf_inventory_path:
- tf_inventory = configparser.ConfigParser(allow_no_value=True)
- tf_inventory.read(tf_inventory_path)
- ip_to_name = {}
- for k, sub in tf_inventory.items():
- if k.startswith('name_'):
- for ip in sub:
- if ip in ip_to_name:
- logger.warning('ip %r already named %r, also got %r', ip, ip_to_name[ip], k)
- ip_to_name[ip] = k
- #logger.debug('names: %r', sorted(ip_to_name.values()))
- #logger.debug('ip to name %r', ip_to_name)
+ ip_to_name = terraform_inventory_ip_not_names(tf_inventory_path)
unfound = []
for ip, name in ip_to_name.items():
found = []
diff --git a/test/heapwatch/metrics_viz.py b/test/heapwatch/metrics_viz.py
new file mode 100644
index 0000000000..584fc0ae59
--- /dev/null
+++ b/test/heapwatch/metrics_viz.py
@@ -0,0 +1,210 @@
+"""
+Tool for metrics files visualization.
+Expects metrics files in format ._.metrics like Primary.20230804_182932.metrics
+Works with metrics collected by heapWatch.py.
+
+Example usage for local net:
+python3 ./test/heapwatch/heapWatch.py --period 10 --metrics --blockinfo --runtime 20m -o nodedata ~/networks/mylocalnet/Primary
+python3 ./test/heapwatch/metrics_viz.py -d nodedata algod_transaction_messages_handled algod_tx_pool_count algod_transaction_messages_backlog_size algod_go_memory_classes_total_bytes
+
+Also works with bdevscripts for cluster tests since it uses heapWatch.py for metrics collection.
+"""
+
+import argparse
+from datetime import datetime
+import glob
+import logging
+import os
+import re
+import time
+from typing import Dict, Iterable, Tuple
+import sys
+
+import dash
+from dash import dcc, html
+import plotly.graph_objs as go
+from plotly.subplots import make_subplots
+
+from metrics_delta import metric_line_re, num, terraform_inventory_ip_not_names
+from client_ram_report import dapp
+
+logger = logging.getLogger(__name__)
+
+metrics_fname_re = re.compile(r'(.*?)\.(\d+_\d+)\.metrics')
+
+def gather_metrics_files_by_nick(metrics_files: Iterable[str]) -> Dict[str, Dict[datetime, str]]:
+ """return {"node nickname": {datetime: path, ...}, ...}}"""
+ filesByNick = {}
+ tf_inventory_path = None
+ for path in metrics_files:
+ fname = os.path.basename(path)
+ if fname == 'terraform-inventory.host':
+ tf_inventory_path = path
+ continue
+ m = metrics_fname_re.match(fname)
+ if not m:
+ continue
+ nick = m.group(1)
+ timestamp = m.group(2)
+ timestamp = datetime.strptime(timestamp, '%Y%m%d_%H%M%S')
+ dapp(filesByNick, nick, timestamp, path)
+ return tf_inventory_path, filesByNick
+
+
+TYPE_GAUGE = 0
+TYPE_COUNTER = 1
+
+def parse_metrics(fin: Iterable[str], nick: str, metrics_names: set=None, diff: bool=None) -> Tuple[Dict[str, float], Dict[str, int]]:
+ """Parse metrics file and return dicts of values and types"""
+ out = {}
+ types = {}
+ try:
+ last_type = None
+ for line in fin:
+ if not line:
+ continue
+ line = line.strip()
+ if not line:
+ continue
+ if line[0] == '#':
+ if line.startswith('# TYPE'):
+ tpe = line.split()[-1]
+ if tpe == 'gauge':
+ last_type = TYPE_GAUGE
+ elif tpe == 'counter':
+ last_type = TYPE_COUNTER
+ continue
+ m = metric_line_re.match(line)
+ if m:
+ name = m.group(1)
+ value = num(m.group(2))
+ else:
+ ab = line.split()
+ name = ab[0]
+ value = num(ab[1])
+
+ det_idx = name.find('{')
+ if det_idx != -1:
+ name = name[:det_idx]
+ fullname = f'{name}{{n={nick}}}'
+ if not metrics_names or name in metrics_names:
+ out[fullname] = value
+ types[fullname] = last_type
+ except:
+ print(f'An exception occurred in parse_metrics: {sys.exc_info()}')
+ pass
+ if diff and metrics_names and len(metrics_names) == 2 and len(out) == 2:
+ m = list(out.keys())
+ name = f'{m[0]}_-_{m[1]}'
+ new_out = {name: out[m[0]] - out[m[1]]}
+ new_types = {name: TYPE_GAUGE}
+ out = new_out
+ types = new_types
+
+ return out, types
+
+
+def main():
+ os.environ['TZ'] = 'UTC'
+ time.tzset()
+ default_output_file = 'metrics_viz.png'
+
+ ap = argparse.ArgumentParser()
+ ap.add_argument('metrics_names', nargs='+', default=None, help='metric name(s) to track')
+ ap.add_argument('-d', '--dir', type=str, default=None, help='dir path to find /*.metrics in')
+ ap.add_argument('-l', '--list-nodes', default=False, action='store_true', help='list available node names with metrics')
+ ap.add_argument('-s', '--save', action='store_true', default=None, help=f'save plot to \'{default_output_file}\' file instead of showing it')
+ ap.add_argument('--diff', action='store_true', default=None, help='diff two gauge metrics instead of plotting their values. Requires two metrics names to be set')
+ ap.add_argument('--verbose', default=False, action='store_true')
+
+ args = ap.parse_args()
+ if args.verbose:
+ logging.basicConfig(level=logging.DEBUG)
+ else:
+ logging.basicConfig(level=logging.INFO)
+
+ if not args.dir:
+ logging.error('need at least one dir set with -d/--dir')
+ return 1
+
+ metrics_files = sorted(glob.glob(os.path.join(args.dir, '*.metrics')))
+ tf_inventory_path, filesByNick = gather_metrics_files_by_nick(metrics_files)
+ if tf_inventory_path:
+ # remap ip addresses to node names
+ ip_to_name = terraform_inventory_ip_not_names(tf_inventory_path)
+ for nick in filesByNick.keys():
+ name = ip_to_name.get(nick)
+ if name:
+ val = filesByNick[nick]
+ filesByNick[name] = val
+ del filesByNick[nick]
+
+ if args.list_nodes:
+ print('Available nodes:', ', '.join(sorted(filesByNick.keys())))
+ return 0
+
+ app = dash.Dash(__name__)
+ app.layout = html.Div(
+ html.Div([
+ html.H4('Algod Metrics'),
+ html.Div(id='text'),
+ dcc.Graph(id='graph'),
+ ])
+ )
+ metrics_names = set(args.metrics_names)
+ nrows = 1 if args.diff and len(args.metrics_names) == 2 else len(metrics_names)
+
+ fig = make_subplots(
+ rows=nrows, cols=1,
+ vertical_spacing=0.03, shared_xaxes=True)
+
+ fig['layout']['margin'] = {
+ 'l': 30, 'r': 10, 'b': 10, 't': 10
+ }
+ fig['layout']['height'] = 500 * nrows
+ # fig.update_layout(template="plotly_dark")
+
+ data = {
+ 'time': [],
+ }
+ raw_series = {}
+ for nick, items in filesByNick.items():
+ active_metrics = set()
+ for dt, metrics_file in items.items():
+ data['time'].append(dt)
+ with open(metrics_file, 'rt') as f:
+ metrics, types = parse_metrics(f, nick, metrics_names, args.diff)
+ for metric_name, metric_value in metrics.items():
+ raw_value = metric_value
+ if metric_name not in data:
+ data[metric_name] = []
+ raw_series[metric_name] = []
+ if types[metric_name] == TYPE_COUNTER:
+ if len(raw_series[metric_name]) > 0:
+ metric_value = (metric_value - raw_series[metric_name][-1]) / (dt - data['time'][-2]).total_seconds()
+ else:
+ metric_value = 0
+ data[metric_name].append(metric_value)
+ raw_series[metric_name].append(raw_value)
+
+ active_metrics.add(metric_name)
+
+ for i, metric in enumerate(sorted(active_metrics)):
+ fig.append_trace(go.Scatter(
+ x=data['time'],
+ y=data[metric],
+ name=metric,
+ mode='lines+markers',
+ line=dict(width=1),
+ ), i+1, 1)
+
+ if args.save:
+ fig.write_image(os.path.join(args.dir, default_output_file))
+ else:
+ fig.show()
+
+ # app.run_server(debug=True)
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
\ No newline at end of file
diff --git a/test/heapwatch/plot_crr_csv.py b/test/heapwatch/plot_crr_csv.py
index d546aaff3a..9e1a471342 100755
--- a/test/heapwatch/plot_crr_csv.py
+++ b/test/heapwatch/plot_crr_csv.py
@@ -63,6 +63,8 @@ def main():
fvals = {}
minv = None
maxv = None
+ minr = None
+ maxr = None
with open(fname) as fin:
reader = csv.DictReader(fin)
for rec in reader:
@@ -84,13 +86,16 @@ def main():
minv = smin(minv, v)
maxv = smax(maxv, v)
+ minr = smin(minr, xround)
+ maxr = smax(maxr, xround)
if not fvals:
print(f"{fname} empty")
continue
nodes = sorted(fvals.keys())
- print("{} found series {}".format(fname, nodes))
+ print("{} found series {} ({} - {})".format(fname, nodes, minr, maxr))
fig, ax = plt.subplots()
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
+ ax.set_xlim([minr, maxr])
ax.yaxis.set_major_formatter(FuncFormatter(format_mem))
ax.set_ylabel('bytes')
ax.set_xlabel('round')
@@ -118,7 +123,7 @@ def main():
xy = metrics[metric]
ax.plot([p[0] for p in xy], [p[1] for p in xy], label=f'{k}/{metric}', color=lc, linestyle=plt_line_styles[i%len(plt_line_styles)])
- ax.legend(loc='upper left', ncol=2)
+ fig.legend(loc='outside upper left', ncol=4)
plt.savefig(fname + '.svg', format='svg')
plt.savefig(fname + '.png', format='png')
#plt.show()
diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt
new file mode 100644
index 0000000000..b46aead08e
--- /dev/null
+++ b/test/heapwatch/requirements.txt
@@ -0,0 +1,7 @@
+dash==2.11.1
+dash-table==5.0.0
+Jinja2==3.1.2
+matplotlib==3.7.2
+plotly==5.16.0
+py-algorand-sdk==2.3.0
+kaleido==0.2.1
diff --git a/test/scripts/e2e_go_tests.sh b/test/scripts/e2e_go_tests.sh
index b1ce3a3559..1d2810f60a 100755
--- a/test/scripts/e2e_go_tests.sh
+++ b/test/scripts/e2e_go_tests.sh
@@ -21,7 +21,6 @@ echo "GOTESTCOMMAND will be: ${GOTESTCOMMAND}"
TESTPATTERNS=()
NORACEBUILD=""
-export RUN_EXPECT="FALSE"
while [ "$1" != "" ]; do
case "$1" in
-e)
@@ -48,6 +47,10 @@ if [[ -n $TESTPATTERNS && -n $RUN_EXPECT ]]; then
exit 1
fi
+if [[ -z $RUN_EXPECT ]]; then
+ RUN_EXPECT="FALSE"
+fi
+
# Anchor our repo root reference location
REPO_ROOT="$( cd "$(dirname "$0")" ; pwd -P )"/../..
diff --git a/test/scripts/e2e_subs/e2e-teal.sh b/test/scripts/e2e_subs/e2e-teal.sh
index 48da729512..7a5975dc9d 100755
--- a/test/scripts/e2e_subs/e2e-teal.sh
+++ b/test/scripts/e2e_subs/e2e-teal.sh
@@ -157,7 +157,14 @@ printf '\x02' | dd of=${TEMPDIR}/true2.lsig bs=1 seek=0 count=1 conv=notrunc
${gcmd} clerk compile ${TEAL}/quine.teal -m
trap 'rm ${TEAL}/quine.teal.*' EXIT
if ! diff ${TEAL}/quine.map ${TEAL}/quine.teal.tok.map; then
- echo "produced source maps do not match"
+ echo "produced source maps do not match: ${TEAL}/quine.map vs ${TEAL}/quine.teal.tok.map"
+ exit 1
+fi
+
+${gcmd} clerk compile ${TEAL}/sourcemap-test.teal -m
+trap 'rm ${TEAL}/sourcemap-test.teal.*' EXIT
+if ! diff ${TEAL}/sourcemap-test.map ${TEAL}/sourcemap-test.teal.tok.map; then
+ echo "produced source maps do not match: ${TEAL}/sourcemap-test.map vs ${TEAL}/sourcemap-test.teal.tok.map"
exit 1
fi
diff --git a/test/scripts/e2e_subs/tealprogs/quine.map b/test/scripts/e2e_subs/tealprogs/quine.map
index 62ea5cc401..c53ca2b928 100644
--- a/test/scripts/e2e_subs/tealprogs/quine.map
+++ b/test/scripts/e2e_subs/tealprogs/quine.map
@@ -1 +1 @@
-{"version":3,"sources":["test/scripts/e2e_subs/tealprogs/quine.teal"],"names":[],"mappings":";AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;AACA;;;AACA;;;AACA;AACA;;AACA;AACA;;;AACA;AACA;AACA;;AACA;;AACA;AACA;AACA"}
\ No newline at end of file
+{"version":3,"sources":["quine.teal"],"names":[],"mappings":";AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;AACA;;;AACA;;;AACA;AACA;;AACA;AACA;;;AACA;AACA;AACA;;AACA;;AACA;AACA;AACA"}
\ No newline at end of file
diff --git a/test/scripts/e2e_subs/tealprogs/sourcemap-test.map b/test/scripts/e2e_subs/tealprogs/sourcemap-test.map
new file mode 100644
index 0000000000..8ec0931089
--- /dev/null
+++ b/test/scripts/e2e_subs/tealprogs/sourcemap-test.map
@@ -0,0 +1 @@
+{"version":3,"sources":["sourcemap-test.teal"],"names":[],"mappings":";;;;AAGA;;AAAmB;;AAAO;AAAI;;;AAE9B;;AAAO;;AAAO;AACd;;AAAO;AAAO;AALO;AAAI;AASrB;AACA"}
\ No newline at end of file
diff --git a/test/scripts/e2e_subs/tealprogs/sourcemap-test.teal b/test/scripts/e2e_subs/tealprogs/sourcemap-test.teal
new file mode 100644
index 0000000000..11a916d92a
--- /dev/null
+++ b/test/scripts/e2e_subs/tealprogs/sourcemap-test.teal
@@ -0,0 +1,12 @@
+#pragma version 9
+#define assertEquals ==; assert
+
+txn ApplicationID; int 0; ==; bz create
+
+int 3; int 4; +;
+int 6; int 1; +;
+assertEquals
+
+create:
+ int 1
+ return
diff --git a/test/scripts/test_private_network.sh b/test/scripts/test_private_network.sh
index f1adc7f62b..72f0bd9160 100755
--- a/test/scripts/test_private_network.sh
+++ b/test/scripts/test_private_network.sh
@@ -1,8 +1,10 @@
#!/usr/bin/env bash
+
+set -euf -o pipefail
+
echo "######################################################################"
echo " test_private_network"
echo "######################################################################"
-set -e
# Suppress telemetry reporting for tests
export ALGOTEST=1
diff --git a/test/testdata/configs/config-v29.json b/test/testdata/configs/config-v29.json
index 8522011ce6..f08b782bbe 100644
--- a/test/testdata/configs/config-v29.json
+++ b/test/testdata/configs/config-v29.json
@@ -48,6 +48,7 @@
"EnableLedgerService": false,
"EnableMetricReporting": false,
"EnableOutgoingNetworkMessageFiltering": true,
+ "EnableP2P": false,
"EnablePingHandler": true,
"EnableProcessBlockStats": false,
"EnableProfiler": false,
@@ -85,8 +86,7 @@
"OptimizeAccountsDatabaseOnStartup": false,
"OutgoingMessageFilterBucketCount": 3,
"OutgoingMessageFilterBucketSize": 128,
- "P2PEnable": false,
- "P2PPersistPeerID": true,
+ "P2PPersistPeerID": false,
"P2PPrivateKeyLocation": "",
"ParticipationKeysRefreshInterval": 60000000000,
"PeerConnectionsUpdateInterval": 3600,
diff --git a/test/testdata/configs/config-v30.json b/test/testdata/configs/config-v30.json
new file mode 100644
index 0000000000..5021c8fd40
--- /dev/null
+++ b/test/testdata/configs/config-v30.json
@@ -0,0 +1,125 @@
+{
+ "Version": 30,
+ "AccountUpdatesStatsInterval": 5000000000,
+ "AccountsRebuildSynchronousMode": 1,
+ "AgreementIncomingBundlesQueueLength": 15,
+ "AgreementIncomingProposalsQueueLength": 50,
+ "AgreementIncomingVotesQueueLength": 20000,
+ "AnnounceParticipationKey": true,
+ "Archival": false,
+ "BaseLoggerDebugLevel": 4,
+ "BlockServiceCustomFallbackEndpoints": "",
+ "BlockServiceMemCap": 500000000,
+ "BroadcastConnectionsLimit": -1,
+ "CadaverDirectory": "",
+ "CadaverSizeTarget": 0,
+ "CatchpointFileHistoryLength": 365,
+ "CatchpointInterval": 10000,
+ "CatchpointTracking": 0,
+ "CatchupBlockDownloadRetryAttempts": 1000,
+ "CatchupBlockValidateMode": 0,
+ "CatchupFailurePeerRefreshRate": 10,
+ "CatchupGossipBlockFetchTimeoutSec": 4,
+ "CatchupHTTPBlockFetchTimeoutSec": 4,
+ "CatchupLedgerDownloadRetryAttempts": 50,
+ "CatchupParallelBlocks": 16,
+ "ConnectionsRateLimitingCount": 60,
+ "ConnectionsRateLimitingWindowSeconds": 1,
+ "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)",
+ "DNSSecurityFlags": 1,
+ "DeadlockDetection": 0,
+ "DeadlockDetectionThreshold": 30,
+ "DisableAPIAuth": false,
+ "DisableLedgerLRUCache": false,
+ "DisableLocalhostConnectionRateLimit": true,
+ "DisableNetworking": false,
+ "DisableOutgoingConnectionThrottling": false,
+ "EnableAccountUpdatesStats": false,
+ "EnableAgreementReporting": false,
+ "EnableAgreementTimeMetrics": false,
+ "EnableAssembleStats": false,
+ "EnableBlockService": false,
+ "EnableBlockServiceFallbackToArchiver": true,
+ "EnableCatchupFromArchiveServers": false,
+ "EnableDeveloperAPI": false,
+ "EnableExperimentalAPI": false,
+ "EnableFollowMode": false,
+ "EnableGossipBlockService": true,
+ "EnableIncomingMessageFilter": false,
+ "EnableLedgerService": false,
+ "EnableMetricReporting": false,
+ "EnableOutgoingNetworkMessageFiltering": true,
+ "EnablePingHandler": true,
+ "EnableProcessBlockStats": false,
+ "EnableProfiler": false,
+ "EnableRequestLogger": false,
+ "EnableRuntimeMetrics": false,
+ "EnableTopAccountsReporting": false,
+ "EnableTxBacklogRateLimiting": true,
+ "EnableTxnEvalTracer": false,
+ "EnableUsageLog": false,
+ "EnableVerbosedTransactionSyncLogging": false,
+ "EndpointAddress": "127.0.0.1:0",
+ "FallbackDNSResolverAddress": "",
+ "ForceFetchTransactions": false,
+ "ForceRelayMessages": false,
+ "GossipFanout": 4,
+ "HeartbeatUpdateInterval": 600,
+ "IncomingConnectionsLimit": 2400,
+ "IncomingMessageFilterBucketCount": 5,
+ "IncomingMessageFilterBucketSize": 512,
+ "LedgerSynchronousMode": 2,
+ "LogArchiveMaxAge": "",
+ "LogArchiveName": "node.archive.log",
+ "LogSizeLimit": 1073741824,
+ "MaxAPIBoxPerApplication": 100000,
+ "MaxAPIResourcesPerAccount": 100000,
+ "MaxAcctLookback": 4,
+ "MaxCatchpointDownloadDuration": 43200000000000,
+ "MaxConnectionsPerIP": 15,
+ "MinCatchpointFileDownloadBytesPerSecond": 20480,
+ "NetAddress": "",
+ "NetworkMessageTraceServer": "",
+ "NetworkProtocolVersion": "",
+ "NodeExporterListenAddress": ":9100",
+ "NodeExporterPath": "./node_exporter",
+ "OptimizeAccountsDatabaseOnStartup": false,
+ "OutgoingMessageFilterBucketCount": 3,
+ "OutgoingMessageFilterBucketSize": 128,
+ "P2PEnable": false,
+ "P2PPersistPeerID": false,
+ "P2PPrivateKeyLocation": "",
+ "ParticipationKeysRefreshInterval": 60000000000,
+ "PeerConnectionsUpdateInterval": 3600,
+ "PeerPingPeriodSeconds": 0,
+ "PriorityPeers": {},
+ "ProposalAssemblyTime": 500000000,
+ "PublicAddress": "",
+ "ReconnectTime": 60000000000,
+ "ReservedFDs": 256,
+ "RestConnectionsHardLimit": 2048,
+ "RestConnectionsSoftLimit": 1024,
+ "RestReadTimeoutSeconds": 15,
+ "RestWriteTimeoutSeconds": 120,
+ "RunHosted": false,
+ "StorageEngine": "sqlite",
+ "SuggestedFeeBlockHistory": 3,
+ "SuggestedFeeSlidingWindowSize": 50,
+ "TLSCertFile": "",
+ "TLSKeyFile": "",
+ "TelemetryToLog": true,
+ "TransactionSyncDataExchangeRate": 0,
+ "TransactionSyncSignificantMessageThreshold": 0,
+ "TxBacklogReservedCapacityPerPeer": 20,
+ "TxBacklogServiceRateWindowSeconds": 10,
+ "TxBacklogSize": 26000,
+ "TxIncomingFilterMaxSize": 500000,
+ "TxIncomingFilteringFlags": 1,
+ "TxPoolExponentialIncreaseFactor": 2,
+ "TxPoolSize": 75000,
+ "TxSyncIntervalSeconds": 60,
+ "TxSyncServeResponseSize": 1000000,
+ "TxSyncTimeoutSeconds": 30,
+ "UseXForwardedForAddressField": "",
+ "VerifiedTranscationsCacheSize": 150000
+}
diff --git a/test/testdata/configs/config-v31.json b/test/testdata/configs/config-v31.json
new file mode 100644
index 0000000000..fccf558c44
--- /dev/null
+++ b/test/testdata/configs/config-v31.json
@@ -0,0 +1,135 @@
+{
+ "Version": 31,
+ "AccountUpdatesStatsInterval": 5000000000,
+ "AccountsRebuildSynchronousMode": 1,
+ "AgreementIncomingBundlesQueueLength": 15,
+ "AgreementIncomingProposalsQueueLength": 50,
+ "AgreementIncomingVotesQueueLength": 20000,
+ "AnnounceParticipationKey": true,
+ "Archival": false,
+ "BaseLoggerDebugLevel": 4,
+ "BlockDBDir": "",
+ "BlockServiceCustomFallbackEndpoints": "",
+ "BlockServiceMemCap": 500000000,
+ "BroadcastConnectionsLimit": -1,
+ "CadaverDirectory": "",
+ "CadaverSizeTarget": 0,
+ "CatchpointDir": "",
+ "CatchpointFileHistoryLength": 365,
+ "CatchpointInterval": 10000,
+ "CatchpointTracking": 0,
+ "CatchupBlockDownloadRetryAttempts": 1000,
+ "CatchupBlockValidateMode": 0,
+ "CatchupFailurePeerRefreshRate": 10,
+ "CatchupGossipBlockFetchTimeoutSec": 4,
+ "CatchupHTTPBlockFetchTimeoutSec": 4,
+ "CatchupLedgerDownloadRetryAttempts": 50,
+ "CatchupParallelBlocks": 16,
+ "ColdDataDir": "",
+ "ConnectionsRateLimitingCount": 60,
+ "ConnectionsRateLimitingWindowSeconds": 1,
+ "CrashDBDir": "",
+ "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)",
+ "DNSSecurityFlags": 1,
+ "DeadlockDetection": 0,
+ "DeadlockDetectionThreshold": 30,
+ "DisableAPIAuth": false,
+ "DisableLedgerLRUCache": false,
+ "DisableLocalhostConnectionRateLimit": true,
+ "DisableNetworking": false,
+ "DisableOutgoingConnectionThrottling": false,
+ "EnableAccountUpdatesStats": false,
+ "EnableAgreementReporting": false,
+ "EnableAgreementTimeMetrics": false,
+ "EnableAssembleStats": false,
+ "EnableBlockService": false,
+ "EnableBlockServiceFallbackToArchiver": false,
+ "EnableCatchupFromArchiveServers": false,
+ "EnableDeveloperAPI": false,
+ "EnableExperimentalAPI": false,
+ "EnableFollowMode": false,
+ "EnableGossipBlockService": true,
+ "EnableIncomingMessageFilter": false,
+ "EnableLedgerService": false,
+ "EnableMetricReporting": false,
+ "EnableOutgoingNetworkMessageFiltering": true,
+ "EnableP2P": false,
+ "EnablePingHandler": true,
+ "EnableProcessBlockStats": false,
+ "EnableProfiler": false,
+ "EnableRequestLogger": false,
+ "EnableRuntimeMetrics": false,
+ "EnableTopAccountsReporting": false,
+ "EnableTxBacklogRateLimiting": true,
+ "EnableTxnEvalTracer": false,
+ "EnableUsageLog": false,
+ "EnableVerbosedTransactionSyncLogging": false,
+ "EndpointAddress": "127.0.0.1:0",
+ "FallbackDNSResolverAddress": "",
+ "ForceFetchTransactions": false,
+ "ForceRelayMessages": false,
+ "GossipFanout": 4,
+ "HeartbeatUpdateInterval": 600,
+ "HotDataDir": "",
+ "IncomingConnectionsLimit": 2400,
+ "IncomingMessageFilterBucketCount": 5,
+ "IncomingMessageFilterBucketSize": 512,
+ "LedgerSynchronousMode": 2,
+ "LogArchiveDir": "",
+ "LogArchiveMaxAge": "",
+ "LogArchiveName": "node.archive.log",
+ "LogFileDir": "",
+ "LogSizeLimit": 1073741824,
+ "MaxAPIBoxPerApplication": 100000,
+ "MaxAPIResourcesPerAccount": 100000,
+ "MaxAcctLookback": 4,
+ "MaxBlockHistoryLookback": 0,
+ "MaxCatchpointDownloadDuration": 43200000000000,
+ "MaxConnectionsPerIP": 15,
+ "MinCatchpointFileDownloadBytesPerSecond": 20480,
+ "NetAddress": "",
+ "NetworkMessageTraceServer": "",
+ "NetworkProtocolVersion": "",
+ "NodeExporterListenAddress": ":9100",
+ "NodeExporterPath": "./node_exporter",
+ "OptimizeAccountsDatabaseOnStartup": false,
+ "OutgoingMessageFilterBucketCount": 3,
+ "OutgoingMessageFilterBucketSize": 128,
+ "P2PPersistPeerID": false,
+ "P2PPrivateKeyLocation": "",
+ "ParticipationKeysRefreshInterval": 60000000000,
+ "PeerConnectionsUpdateInterval": 3600,
+ "PeerPingPeriodSeconds": 0,
+ "PriorityPeers": {},
+ "ProposalAssemblyTime": 500000000,
+ "PublicAddress": "",
+ "ReconnectTime": 60000000000,
+ "ReservedFDs": 256,
+ "RestConnectionsHardLimit": 2048,
+ "RestConnectionsSoftLimit": 1024,
+ "RestReadTimeoutSeconds": 15,
+ "RestWriteTimeoutSeconds": 120,
+ "RunHosted": false,
+ "StateproofDir": "",
+ "StorageEngine": "sqlite",
+ "SuggestedFeeBlockHistory": 3,
+ "SuggestedFeeSlidingWindowSize": 50,
+ "TLSCertFile": "",
+ "TLSKeyFile": "",
+ "TelemetryToLog": true,
+ "TrackerDBDir": "",
+ "TransactionSyncDataExchangeRate": 0,
+ "TransactionSyncSignificantMessageThreshold": 0,
+ "TxBacklogReservedCapacityPerPeer": 20,
+ "TxBacklogServiceRateWindowSeconds": 10,
+ "TxBacklogSize": 26000,
+ "TxIncomingFilterMaxSize": 500000,
+ "TxIncomingFilteringFlags": 1,
+ "TxPoolExponentialIncreaseFactor": 2,
+ "TxPoolSize": 75000,
+ "TxSyncIntervalSeconds": 60,
+ "TxSyncServeResponseSize": 1000000,
+ "TxSyncTimeoutSeconds": 30,
+ "UseXForwardedForAddressField": "",
+ "VerifiedTranscationsCacheSize": 150000
+}
diff --git a/test/testdata/configs/config-v32.json b/test/testdata/configs/config-v32.json
new file mode 100644
index 0000000000..ce02380331
--- /dev/null
+++ b/test/testdata/configs/config-v32.json
@@ -0,0 +1,139 @@
+{
+ "Version": 32,
+ "AccountUpdatesStatsInterval": 5000000000,
+ "AccountsRebuildSynchronousMode": 1,
+ "AgreementIncomingBundlesQueueLength": 15,
+ "AgreementIncomingProposalsQueueLength": 50,
+ "AgreementIncomingVotesQueueLength": 20000,
+ "AnnounceParticipationKey": true,
+ "Archival": false,
+ "BaseLoggerDebugLevel": 4,
+ "BlockDBDir": "",
+ "BlockServiceCustomFallbackEndpoints": "",
+ "BlockServiceMemCap": 500000000,
+ "BroadcastConnectionsLimit": -1,
+ "CadaverDirectory": "",
+ "CadaverSizeTarget": 0,
+ "CatchpointDir": "",
+ "CatchpointFileHistoryLength": 365,
+ "CatchpointInterval": 10000,
+ "CatchpointTracking": 0,
+ "CatchupBlockDownloadRetryAttempts": 1000,
+ "CatchupBlockValidateMode": 0,
+ "CatchupFailurePeerRefreshRate": 10,
+ "CatchupGossipBlockFetchTimeoutSec": 4,
+ "CatchupHTTPBlockFetchTimeoutSec": 4,
+ "CatchupLedgerDownloadRetryAttempts": 50,
+ "CatchupParallelBlocks": 16,
+ "ColdDataDir": "",
+ "ConnectionsRateLimitingCount": 60,
+ "ConnectionsRateLimitingWindowSeconds": 1,
+ "CrashDBDir": "",
+ "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)",
+ "DNSSecurityFlags": 1,
+ "DeadlockDetection": 0,
+ "DeadlockDetectionThreshold": 30,
+ "DisableAPIAuth": false,
+ "DisableLedgerLRUCache": false,
+ "DisableLocalhostConnectionRateLimit": true,
+ "DisableNetworking": false,
+ "DisableOutgoingConnectionThrottling": false,
+ "EnableAccountUpdatesStats": false,
+ "EnableAgreementReporting": false,
+ "EnableAgreementTimeMetrics": false,
+ "EnableAssembleStats": false,
+ "EnableBlockService": false,
+ "EnableBlockServiceFallbackToArchiver": false,
+ "EnableCatchupFromArchiveServers": false,
+ "EnableDeveloperAPI": false,
+ "EnableExperimentalAPI": false,
+ "EnableFollowMode": false,
+ "EnableGossipBlockService": true,
+ "EnableIncomingMessageFilter": false,
+ "EnableLedgerService": false,
+ "EnableMetricReporting": false,
+ "EnableOutgoingNetworkMessageFiltering": true,
+ "EnableP2P": false,
+ "EnablePingHandler": true,
+ "EnableProcessBlockStats": false,
+ "EnableProfiler": false,
+ "EnableRequestLogger": false,
+ "EnableRuntimeMetrics": false,
+ "EnableTopAccountsReporting": false,
+ "EnableTxBacklogAppRateLimiting": true,
+ "EnableTxBacklogRateLimiting": true,
+ "EnableTxnEvalTracer": false,
+ "EnableUsageLog": false,
+ "EnableVerbosedTransactionSyncLogging": false,
+ "EndpointAddress": "127.0.0.1:0",
+ "FallbackDNSResolverAddress": "",
+ "ForceFetchTransactions": false,
+ "ForceRelayMessages": false,
+ "GossipFanout": 4,
+ "HeartbeatUpdateInterval": 600,
+ "HotDataDir": "",
+ "IncomingConnectionsLimit": 2400,
+ "IncomingMessageFilterBucketCount": 5,
+ "IncomingMessageFilterBucketSize": 512,
+ "LedgerSynchronousMode": 2,
+ "LogArchiveDir": "",
+ "LogArchiveMaxAge": "",
+ "LogArchiveName": "node.archive.log",
+ "LogFileDir": "",
+ "LogSizeLimit": 1073741824,
+ "MaxAPIBoxPerApplication": 100000,
+ "MaxAPIResourcesPerAccount": 100000,
+ "MaxAcctLookback": 4,
+ "MaxBlockHistoryLookback": 0,
+ "MaxCatchpointDownloadDuration": 43200000000000,
+ "MaxConnectionsPerIP": 15,
+ "MinCatchpointFileDownloadBytesPerSecond": 20480,
+ "NetAddress": "",
+ "NetworkMessageTraceServer": "",
+ "NetworkProtocolVersion": "",
+ "NodeExporterListenAddress": ":9100",
+ "NodeExporterPath": "./node_exporter",
+ "OptimizeAccountsDatabaseOnStartup": false,
+ "OutgoingMessageFilterBucketCount": 3,
+ "OutgoingMessageFilterBucketSize": 128,
+ "P2PPersistPeerID": false,
+ "P2PPrivateKeyLocation": "",
+ "ParticipationKeysRefreshInterval": 60000000000,
+ "PeerConnectionsUpdateInterval": 3600,
+ "PeerPingPeriodSeconds": 0,
+ "PriorityPeers": {},
+ "ProposalAssemblyTime": 500000000,
+ "PublicAddress": "",
+ "ReconnectTime": 60000000000,
+ "ReservedFDs": 256,
+ "RestConnectionsHardLimit": 2048,
+ "RestConnectionsSoftLimit": 1024,
+ "RestReadTimeoutSeconds": 15,
+ "RestWriteTimeoutSeconds": 120,
+ "RunHosted": false,
+ "StateproofDir": "",
+ "StorageEngine": "sqlite",
+ "SuggestedFeeBlockHistory": 3,
+ "SuggestedFeeSlidingWindowSize": 50,
+ "TLSCertFile": "",
+ "TLSKeyFile": "",
+ "TelemetryToLog": true,
+ "TrackerDBDir": "",
+ "TransactionSyncDataExchangeRate": 0,
+ "TransactionSyncSignificantMessageThreshold": 0,
+ "TxBacklogAppTxPerSecondRate": 100,
+ "TxBacklogAppTxRateLimiterMaxSize": 1048576,
+ "TxBacklogRateLimitingCongestionPct": 50,
+ "TxBacklogReservedCapacityPerPeer": 20,
+ "TxBacklogServiceRateWindowSeconds": 10,
+ "TxBacklogSize": 26000,
+ "TxIncomingFilterMaxSize": 500000,
+ "TxIncomingFilteringFlags": 1,
+ "TxPoolExponentialIncreaseFactor": 2,
+ "TxPoolSize": 75000,
+ "TxSyncIntervalSeconds": 60,
+ "TxSyncServeResponseSize": 1000000,
+ "TxSyncTimeoutSeconds": 30,
+ "UseXForwardedForAddressField": "",
+ "VerifiedTranscationsCacheSize": 150000
+}
diff --git a/test/testdata/nettemplates/DisableAPIAuth.json b/test/testdata/nettemplates/DisableAPIAuth.json
new file mode 100644
index 0000000000..4d1b859b41
--- /dev/null
+++ b/test/testdata/nettemplates/DisableAPIAuth.json
@@ -0,0 +1,25 @@
+{
+ "Genesis": {
+ "NetworkName": "tbd",
+ "ConsensusProtocol": "future",
+ "LastPartKeyRound": 3000,
+ "Wallets": [
+ {
+ "Name": "Wallet1",
+ "Stake": 100,
+ "Online": true
+ }
+ ]
+ },
+ "Nodes": [
+ {
+ "Name": "Primary",
+ "IsRelay": true,
+ "ConfigJSONOverride": "{\"DisableAPIAuth\":true}",
+ "Wallets": [
+ { "Name": "Wallet1",
+ "ParticipationOnly": false }
+ ]
+ }
+ ]
+}
diff --git a/test/testdata/nettemplates/FiveNodesP2P.json b/test/testdata/nettemplates/FiveNodesP2P.json
new file mode 100644
index 0000000000..db77120d48
--- /dev/null
+++ b/test/testdata/nettemplates/FiveNodesP2P.json
@@ -0,0 +1,61 @@
+{
+ "Genesis": {
+ "NetworkName": "tbd",
+ "LastPartKeyRound": 5000,
+ "Wallets": [
+ {
+ "Name": "LargeWallet",
+ "Stake": 85,
+ "Online": true
+ },
+ {
+ "Name": "SmallWallet",
+ "Stake": 10,
+ "Online": true
+ },
+ {
+ "Name": "NonPartWallet",
+ "Stake": 5,
+ "Online": true
+ }
+ ]
+ },
+ "Nodes": [
+ {
+ "Name": "Relay1",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "IsRelay": true
+ },
+ {
+ "Name": "Relay2",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "IsRelay": true
+ },
+ {
+ "Name": "PartNode1",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [{
+ "Name": "LargeWallet",
+ "ParticipationOnly": true
+ }],
+ "PeerList": "Relay1;Relay2"
+ },
+ {
+ "Name": "PartNode2",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [{
+ "Name": "SmallWallet",
+ "ParticipationOnly": true
+ }],
+ "PeerList": "Relay2"
+ },
+ {
+ "Name": "NonPartNode",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [{
+ "Name": "NonPartWallet"
+ }],
+ "PeerList": "Relay1"
+ }
+ ]
+}
diff --git a/test/testdata/nettemplates/ThreeNodesEvenDistP2P.json b/test/testdata/nettemplates/ThreeNodesEvenDistP2P.json
new file mode 100644
index 0000000000..f6cd8d3cda
--- /dev/null
+++ b/test/testdata/nettemplates/ThreeNodesEvenDistP2P.json
@@ -0,0 +1,50 @@
+{
+ "Genesis": {
+ "NetworkName": "tbd",
+ "LastPartKeyRound": 3000,
+ "Wallets": [
+ {
+ "Name": "Wallet1",
+ "Stake": 33,
+ "Online": true
+ },
+ {
+ "Name": "Wallet2",
+ "Stake": 33,
+ "Online": true
+ },
+ {
+ "Name": "Wallet3",
+ "Stake": 34,
+ "Online": true
+ }
+ ]
+ },
+ "Nodes": [
+ {
+ "Name": "Primary",
+ "IsRelay": true,
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [
+ { "Name": "Wallet1",
+ "ParticipationOnly": false }
+ ]
+ },
+ {
+ "Name": "Node1",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [
+ { "Name": "Wallet2",
+ "ParticipationOnly": false }
+ ]
+ },
+ {
+ "Name": "Node2",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [
+ { "Name": "Wallet3",
+ "ParticipationOnly": false }
+ ]
+ }
+ ]
+}
diff --git a/test/testdata/nettemplates/TwoNodes50EachP2P.json b/test/testdata/nettemplates/TwoNodes50EachP2P.json
new file mode 100644
index 0000000000..b5a76f6123
--- /dev/null
+++ b/test/testdata/nettemplates/TwoNodes50EachP2P.json
@@ -0,0 +1,37 @@
+{
+ "Genesis": {
+ "NetworkName": "tbd",
+ "LastPartKeyRound": 3000,
+ "Wallets": [
+ {
+ "Name": "Wallet1",
+ "Stake": 50,
+ "Online": true
+ },
+ {
+ "Name": "Wallet2",
+ "Stake": 50,
+ "Online": true
+ }
+ ]
+ },
+ "Nodes": [
+ {
+ "Name": "Primary",
+ "IsRelay": true,
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [
+ { "Name": "Wallet1",
+ "ParticipationOnly": false }
+ ]
+ },
+ {
+ "Name": "Node",
+ "ConfigJSONOverride": "{\"EnableP2P\":true}",
+ "Wallets": [
+ { "Name": "Wallet2",
+ "ParticipationOnly": false }
+ ]
+ }
+ ]
+}
diff --git a/tools/block-generator/Makefile b/tools/block-generator/Makefile
index fdb5754210..8134a2e517 100644
--- a/tools/block-generator/Makefile
+++ b/tools/block-generator/Makefile
@@ -1,9 +1,19 @@
-SCENARIO = scenarios/config.allmixed.small.yml
-SKIP = --skip-runner
+# The following variables are primarily useful for tuning dev testing and
+# appear in the targets pg-up, pg-enter, pg-down, pg-query,
+# run-runner, run-file-exporter, and benchmark-blocks-export:
+SCENARIO = scenarios/benchmarks/stress.50000.yml
RESETDB = --reset-db
+TIMES = 1
REPORTS = ../../tmp/RUN_RUNNER_OUTPUTS
DURATION = 30s
VERBOSE = --verbose
+CONDUIT = ./conduit
+TEMPLATE = # --template file-exporter (default postgres-exporter)
+PGUSER = algorand
+PGDB = generator_db
+PGCONT = "generator-test-container"
+PGCONN = "host=localhost user=$(PGUSER) password=algorand dbname=$(PGDB) port=15432 sslmode=disable"
+PGRUNNER = --postgres-connection-string $(PGCONN)
block-generator: clean-generator
go build
@@ -11,31 +21,38 @@ block-generator: clean-generator
clean-generator:
rm -f block-generator
-debug-blockgen:
- python scripts/run_runner.py \
- --conduit-binary ./conduit \
- --scenario $(SCENARIO) \
- --report-directory $(REPORTS) \
- --keep-alive $(SKIP) \
- --test-duration $(DURATION) \
- $(RESETDB)
+pg-up:
+ docker run --name $(PGCONT) -p 15432:5432 -e POSTGRES_USER=$(PGUSER) -e POSTGRES_PASSWORD=algorand -e POSTGRES_DB=$(PGDB) -d postgres
+
+pg-enter:
+ docker exec -it $(PGCONT) psql -U $(PGUSER) -d $(PGDB)
-enter-pg:
- docker exec -it generator-test-container psql -U algorand -d generator_db
+QUERY := -c "select count(*) from txn;"
+pg-query:
+ psql $(PGCONN) $(QUERY)
-clean-docker:
- docker rm -f generator-test-container
+pg-down:
+ docker rm -f $(PGCONT)
run-runner: block-generator
- ./block-generator runner --conduit-binary ./conduit \
+ ./block-generator runner --conduit-binary $(CONDUIT) \
--keep-data-dir \
--test-duration $(DURATION) \
--conduit-log-level trace \
- --postgres-connection-string "host=localhost user=algorand password=algorand dbname=generator_db port=15432 sslmode=disable" \
+ $(TEMPLATE) \
+ $(PGRUNNER) \
--scenario $(SCENARIO) \
$(RESETDB) \
$(VERBOSE) \
--report-directory $(REPORTS)
+ --times $(TIMES)
+
+run-file-exporter:
+ make run-runner TEMPLATE="--template file-exporter" TIMES=1 RESETDB= PGRUNNER=
+
+BENCHMARK = "organic.25000"
+benchmark-blocks-export: block-generator
+ make run-file-exporter DURATION=60s SCENARIO=scenarios/benchmarks/$(BENCHMARK).yml REPORTS=$(BENCHMARK)
clean-reports:
rm -rf $(REPORTS)
diff --git a/tools/block-generator/README.md b/tools/block-generator/README.md
index a26328ec43..3893c81913 100644
--- a/tools/block-generator/README.md
+++ b/tools/block-generator/README.md
@@ -9,6 +9,7 @@ Several scenarios were designed to mimic different block traffic patterns. Scena
### Organic Traffic
Simulate the current mainnet traffic pattern. Approximately:
+
* 15% payment transactions
* 10% application transactions
* 75% asset transactions
@@ -33,7 +34,7 @@ Block generator uses a YAML config file to describe the composition of each rand
The block generator supports **payment**, **asset**, and **application** transactions. The settings are hopefully, more or less, obvious. Distributions are specified as fractions of 1.0, and the sum of all options must add up to ~1.0.
-Here is an example which uses all of the current options. Notice that the synthetic blocks are not required to follow algod limits, in this case the block size is specified as 99,999:
+Here is an example which uses all of the current options. Notice that the synthetic blocks are not required to follow algod limits, and that in this case the block size is specified as 99,999:
```yml
name: "Mixed (99,999)"
@@ -104,6 +105,7 @@ Flags:
-c, --config string Specify the block configuration yaml file.
-h, --help help for daemon
-p, --port uint Port to start the server at. (default 4010)
+ -v, --verbose If set the daemon will print debugging information from the generator and ledger.
```
### runner
@@ -143,7 +145,7 @@ final_overall_transactions_per_second:8493.40
final_uptime_seconds:3600.06
```
-Here is the help output for **runner**:
+We recommend printing out the help information for the **runner**:
```bash
~$ ./block-generator runner -h
@@ -152,36 +154,19 @@ Run an automated test suite using the block-generator daemon and a provided cond
Usage:
block-generator runner [flags]
-Flags:
- -i, --conduit-binary string Path to conduit binary.
- -l, --conduit-log-level string LogLevel to use when starting Conduit. [panic, fatal, error, warn, info, debug, trace] (default "error")
- --cpuprofile string Path where Conduit writes its CPU profile.
- -f, --genesis-file string file path to the genesis associated with the db snapshot
- -h, --help help for runner
- -k, --keep-data-dir If set the validator will not delete the data directory after tests complete.
- -p, --metrics-port uint Port to start the metrics server at. (default 9999)
- -c, --postgres-connection-string string Postgres connection string.
- -r, --report-directory string Location to place test reports.
- --reset-db If set database will be deleted before running tests.
- --reset-report-dir If set any existing report directory will be deleted before running tests.
- -s, --scenario string Directory containing scenarios, or specific scenario file.
- -d, --test-duration duration Duration to use for each scenario. (default 5m0s)
- --validate If set the validator will run after test-duration has elapsed to verify data is correct. An extra line in each report indicates validator success or failure.
- -v, --verbose If set the runner will print debugging information from the generator and ledger.
- ```
-
-## Example Run using Conduit and Postgres
+... etc ...
+```
+
+## Example Runs using Conduit
A typical **runner** scenario involves:
-* a [scenario configuration](#scenario-configuration) file, e.g. [test_config.yml](./test_config.yml)
-* access to a `conduit` binary to query the block generator's mock Algod endpoint and ingest the synthetic blocks
+* a [scenario configuration](#scenario-configuration) file, e.g. [config.asset.xfer.yml](./scenarios/config.asset.xfer.yml) or for the example below [test_scenario.yml](./generator/test_scenario.yml)
+* access to a `conduit` binary to query the block generator's mock Algod endpoint and ingest the synthetic blocks (below it's assumed to be set in the `CONDUIT_BINARY` environment variable)
* a datastore -such as a postgres database- to collect `conduit`'s output
* a `conduit` config file to define its import/export behavior
-The `block-generator runner` subcommand has a number of options to configure behavion.
-
-### Sample Run
+### Sample Run with Postgres
First you'll need to get a `conduit` binary. For example you can follow the [developer portal's instructions](https://developer.algorand.org/docs/get-details/conduit/GettingStarted/#installation) or run `go build .` inside of the directory `cmd/conduit` after downloading the `conduit` repo.
@@ -204,5 +189,133 @@ block-generator runner \
### Scenario Report
-If all goes well, the run will generate a directory named reports.
+If all goes well, the run will generate a directory named `reports`
+in the same directory in which the command was run.
In that directory you can see the statistics of the run in the file ending with `.report`.
+
+The `block-generator runner` subcommand has a number of options to configure behavior.
+
+## Sample Run with the File Exporter
+
+It's possible to save the generated blocks to the file system.
+This enables running benchmarks and stress tests at a later time and without
+needing a live block generator. The setup is very similar to the previous Postgres example. The main change compared to the previous is to _**specify a different conduit configuration**_ template.
+
+The `block-generator runner` command in this case would look like:
+
+```sh
+block-generator runner \
+ --conduit-binary "$CONDUIT_BINARY" \
+ --report-directory reports \
+ --test-duration 30s \
+ --conduit-log-level trace \
+ --template file-exporter \
+ --keep-data-dir \
+ --scenario generator/test_scenario.yml
+```
+
+### Generated Blocks
+
+If all goes well, the run will generate a directory named `reports`
+in the same directory in which the command was run.
+In addition to the statistical report and run logs,
+there will be a directory ending with `_data` - this is conduit's
+data directory (which is saved thanks to the `--keep-data-dir` flag).
+In that directory under `exporter_file_writer/`
+the generated blocks and a genesis file will be saved.
+
+## Scenario Distribution - Configuration vs. Reality
+
+This section follows up on the [Scenario Configuration](#scenario-configuration) section to detail how each kind of transaction is actually chosen.
+Note that -especially for early rounds- there is no guarantee that the
+percentages of transaction types will resemble the configured distribution.
+
+For example consider the [Organic 25,000](scenarios/benchmarks/organic.25000.yml) scenario:
+
+```yml
+name: "Organic (25000)"
+genesis_accounts: 10000
+genesis_account_balance: 1000000000000
+tx_per_block: 25000
+
+# transaction distribution
+tx_pay_fraction: 0.05
+tx_asset_fraction: 0.75
+tx_app_fraction: 0.20
+
+# payment config
+pay_acct_create_fraction: 0.10
+pay_xfer_fraction: 0.90
+
+# asset config
+asset_create_fraction: 0.001
+asset_optin_fraction: 0.1
+asset_close_fraction: 0.05
+asset_xfer_fraction: 0.849
+asset_delete_fraction: 0
+
+# app kind config
+app_boxes_fraction: 1.0
+app_swap_fraction: 0.0
+
+# app boxes config
+app_boxes_create_fraction: 0.01
+app_boxes_optin_fraction: 0.1
+app_boxes_call_fraction: 0.89
+```
+
+We are _actually_ asking the generator for the following distribution:
+
+* `pay_acct_create_fraction = 0.005 (= 0.05 * 0.10)`
+* `pay_xfer_fraction = 0.045 (= 0.05 * 0.90)`
+* `asset_create_fraction = 0.00075 (= 0.75 * 0.001)`
+* `asset_optin_fraction = 0.075 (= 0.75 * 0.1)`
+* `asset_close_fraction = 0.0375 (= 0.75 * 0.05)`
+* `asset_xfer_fraction = 0.63675 (= 0.75 * 0.849)`
+* `asset_delete_fraction = 0`
+* `app_boxes_create_fraction = 0.002 (= 0.20 * 1.0 * 0.01)`
+* `app_boxes_optin_fraction = 0.02 (= 0.20 * 1.0 * 0.1)`
+* `app_boxes_call_fraction = 0.178 (= 0.20 * 1.0 * 0.89)`
+
+The block generator randomly chooses
+
+1. the transaction type (pay, asset, or app) according to the `transaction distribution`
+2. based on the type:
+
+ a. for payments and assets, the specific type based on the `payment config` and `asset config` distributions
+
+ b. for apps, the app kind (boxes or swaps) based on the `app kind config` distribution
+
+3. For _apps only_: the specific app call based on the `app boxes config` (and perhaps in the future `app swap config`)
+
+As each of the steps above is itself random, we only expect _approximate matching_ to the configured distribution.
+
+Furthermore, for certain asset and app transactions there may be a substitution that occurs based on the type. In particular:
+
+* for **assets**:
+ * when a requested asset txn is **create**, it is never substituted
+ * when there are no assets, an **asset create** is always substituted
+ * when a requested asset txn is **delete** but the creator doesn't hold all asset funds, an **asset close** is substitued (which itself may be substituted using the **close** rule below)
+ * when a requested asset txn is **opt in** but all accounts are already opted in, an **asset close** is substituted (which itself may be substituted using the **close** rule below)
+ * when a requested asset txn is **transfer** but there is only one account holding it, an **asset opt in** is substituted (which itself may be substituted using the **asset opt in** rule above)
+ * when a requested asset txn is **close** but there is only one account holding it, an **asset opt in** is substituted (which itself may be substituted using the **asset opt in** rule above)
+* for **apps**:
+ * when a requested app txn is **create**, it is never substituted
+ * when a requested app txn is **opt in**:
+ * if the sender is already opted in, an **app call** is substituted
+ * otherwise, if the sender's opt-in is pending for the round, an **app create** is substituted
+ * when a requested app txn is **call** but it's not opted into, an **app opt in** is attempted to be substituted (but this may itself be substituted for given the **app opt in** rule above)
+
+Over time, we expect the state of the generator to stabilize so that very few substitutions occur. However, especially for the first few rounds, there may be drastic differences between the config distribution and observed percentages.
+
+In particular:
+
+* for Round 1, all app transactions are replaced by **app create**
+* for Round 2, all **app call** transactions are replaced by **app opt in**
+
+Therefore, for scenarios involving a variety of app transactions, only for Round 3 and higher do we expect to see distributions comparable to those configured.
+
+> NOTE: Even in the steady state, we still expect fundamental deviations
+> from the configured distributions in the cases of apps. This is because
+> an app call may have associated group and inner transactions. For example,
+> if an app call requires 1 sibling asset call in its group and has 2 inner payments, this single app call will generate 1 additional asset txn and 2 payment txns.
diff --git a/tools/block-generator/generator/daemon.go b/tools/block-generator/generator/daemon.go
index fb4f52bab3..0a1371a1bc 100644
--- a/tools/block-generator/generator/daemon.go
+++ b/tools/block-generator/generator/daemon.go
@@ -48,7 +48,7 @@ func init() {
DaemonCmd.Flags().StringVarP(&configFile, "config", "c", "", "Specify the block configuration yaml file.")
DaemonCmd.Flags().Uint64VarP(&port, "port", "p", 4010, "Port to start the server at.")
- DaemonCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "If set the runner will print debugging information from the generator and ledger.")
+ DaemonCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "If set the daemon will print debugging information from the generator and ledger.")
DaemonCmd.MarkFlagRequired("config")
}
diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go
index 22246e047b..44c5444876 100644
--- a/tools/block-generator/generator/generate.go
+++ b/tools/block-generator/generator/generate.go
@@ -17,7 +17,6 @@
package generator
import (
- _ "embed"
"encoding/json"
"errors"
"fmt"
@@ -32,49 +31,19 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
txn "github.com/algorand/go-algorand/data/transactions"
- "github.com/algorand/go-algorand/data/transactions/logic"
"github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/rpcs"
- "github.com/algorand/go-algorand/tools/block-generator/util"
)
-// ---- templates ----
-
-//go:embed teal/poap_boxes.teal
-var approvalBoxes string
-var approvalBoxesBytes interface{}
-
-//go:embed teal/poap_clear.teal
-var clearBoxes string
-var clearBoxesBytes interface{}
-
-//go:embed teal/swap_amm.teal
-var approvalSwap string
-var approvalSwapBytes interface{}
-
-//go:embed teal/swap_clear.teal
-var clearSwap string
-var clearSwapBytes interface{}
-
-func init() {
- prog, err := logic.AssembleString(approvalBoxes)
- util.MaybeFail(err, "failed to assemble approval program")
- approvalBoxesBytes = prog.Program
-
- prog, err = logic.AssembleString(clearBoxes)
- util.MaybeFail(err, "failed to assemble clear program")
- clearBoxesBytes = prog.Program
-
- prog, err = logic.AssembleString(approvalSwap)
- util.MaybeFail(err, "failed to assemble approvalSwap program")
- approvalSwapBytes = prog.Program
-
- prog, err = logic.AssembleString(clearSwap)
- util.MaybeFail(err, "failed to assemble clearSwap program")
- clearSwapBytes = prog.Program
-}
+const (
+ BlockTotalSizeBytes = "blocks_total_size_bytes"
+ CommitWaitTimeMS = "commit_wait_time_ms"
+ BlockgenGenerateTimeMS = "blockgen_generate_time_ms"
+ LedgerEvalTimeMS = "ledger_eval_time_ms"
+ LedgerValidateTimeMS = "ledger_validate_time_ms"
+)
// ---- constructors ----
@@ -105,10 +74,12 @@ func MakeGenerator(log logging.Logger, dbround uint64, bkGenesis bookkeeping.Gen
rewardsResidue: 0,
rewardsRate: 0,
rewardsRecalculationRound: 0,
- reportData: make(map[TxTypeID]TxData),
latestData: make(map[TxTypeID]uint64),
roundOffset: dbround,
}
+ gen.reportData.InitialRound = gen.roundOffset
+ gen.reportData.Transactions = make(map[TxTypeID]TxData)
+ gen.reportData.Counters = make(map[string]uint64)
gen.feeSink[31] = 1
gen.rewardsPool[31] = 2
@@ -357,7 +328,7 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error {
g.setBlockHeader(&cert)
intra := uint64(0)
- txGroupsAD := [][]txn.SignedTxnWithAD{}
+ var txGroupsAD [][]txn.SignedTxnWithAD
for intra < minTxnsForBlock {
txGroupAD, numTxns, err := g.generateTxGroup(g.round, intra)
if err != nil {
@@ -371,8 +342,9 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error {
intra += numTxns
}
generated = time.Now()
+ g.reportData.Counters[BlockgenGenerateTimeMS] += uint64(generated.Sub(start).Milliseconds())
- vBlock, ledgerTxnCount, err := g.evaluateBlock(cert.Block.BlockHeader, txGroupsAD, int(intra))
+ vBlock, ledgerTxnCount, commitWaitTime, err := g.evaluateBlock(cert.Block.BlockHeader, txGroupsAD, int(intra))
if err != nil {
return fmt.Errorf("failed to evaluate block: %w", err)
}
@@ -380,12 +352,15 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error {
return fmt.Errorf("evaluateBlock() txn count mismatches theoretical intra: %d != %d", ledgerTxnCount, g.txnCounter+intra)
}
evaluated = time.Now()
+ g.reportData.Counters[LedgerEvalTimeMS] += uint64(evaluated.Sub(generated).Milliseconds())
err = g.ledger.AddValidatedBlock(*vBlock, cert.Certificate)
if err != nil {
return fmt.Errorf("failed to add validated block: %w", err)
}
validated = time.Now()
+ g.reportData.Counters[CommitWaitTimeMS] += uint64(commitWaitTime.Milliseconds())
+ g.reportData.Counters[LedgerValidateTimeMS] += uint64((validated.Sub(evaluated) - commitWaitTime).Milliseconds())
cert.Block.Payset = vBlock.Block().Payset
@@ -400,6 +375,8 @@ func (g *generator) WriteBlock(output io.Writer, round uint64) error {
// write the msgpack bytes for a block
g.latestBlockMsgp = protocol.EncodeMsgp(&cert)
+ g.reportData.Counters[BlockTotalSizeBytes] += uint64(len(g.latestBlockMsgp))
+
_, err = output.Write(g.latestBlockMsgp)
if err != nil {
return err
@@ -812,7 +789,7 @@ func (g *generator) generateAssetTxnInternalHint(txType TxTypeID, round uint64,
}
if g.balances[senderIndex] < txn.Fee.ToUint64() {
- fmt.Printf("\n\nthe sender account does not have enough algos for the transfer. idx %d, asset transaction type %v, num %d\n\n", senderIndex, actual, g.reportData[actual].GenerationCount)
+ fmt.Printf("\n\nthe sender account does not have enough algos for the transfer. idx %d, asset transaction type %v, num %d\n\n", senderIndex, actual, g.reportData.Transactions[actual].GenerationCount)
os.Exit(1)
}
@@ -835,10 +812,10 @@ func track(id TxTypeID) (TxTypeID, time.Time) {
func (g *generator) recordData(id TxTypeID, start time.Time) {
g.latestData[id]++
- data := g.reportData[id]
+ data := g.reportData.Transactions[id]
data.GenerationCount += 1
data.GenerationTime += time.Since(start)
- g.reportData[id] = data
+ g.reportData.Transactions[id] = data
}
// ---- sign transactions ----
diff --git a/tools/block-generator/generator/generate_apps.go b/tools/block-generator/generator/generate_apps.go
index 5ecac947c0..8edd28da57 100644
--- a/tools/block-generator/generator/generate_apps.go
+++ b/tools/block-generator/generator/generate_apps.go
@@ -17,13 +17,53 @@
package generator
import (
+ _ "embed"
"fmt"
"math/rand"
"time"
txn "github.com/algorand/go-algorand/data/transactions"
+ "github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/tools/block-generator/util"
)
+// ---- templates ----
+
+//go:embed teal/poap_boxes.teal
+var approvalBoxes string
+var approvalBoxesBytes interface{}
+
+//go:embed teal/poap_clear.teal
+var clearBoxes string
+var clearBoxesBytes interface{}
+
+//go:embed teal/swap_amm.teal
+var approvalSwap string
+var approvalSwapBytes interface{}
+
+//go:embed teal/swap_clear.teal
+var clearSwap string
+var clearSwapBytes interface{}
+
+// Precompile teal programs
+func init() {
+ prog, err := logic.AssembleString(approvalBoxes)
+ util.MaybeFail(err, "failed to assemble approval program")
+ approvalBoxesBytes = prog.Program
+
+ prog, err = logic.AssembleString(clearBoxes)
+ util.MaybeFail(err, "failed to assemble clear program")
+ clearBoxesBytes = prog.Program
+
+ prog, err = logic.AssembleString(approvalSwap)
+ util.MaybeFail(err, "failed to assemble approvalSwap program")
+ approvalSwapBytes = prog.Program
+
+ prog, err = logic.AssembleString(clearSwap)
+ util.MaybeFail(err, "failed to assemble clearSwap program")
+ clearSwapBytes = prog.Program
+}
+
// ---- generator app state ----
func (g *generator) resetPendingApps() {
@@ -71,7 +111,7 @@ func countEffects(actual TxTypeID) uint64 {
func CumulativeEffects(report Report) EffectsReport {
effsReport := make(EffectsReport)
- for txType, data := range report {
+ for txType, data := range report.Transactions {
rootCount := data.GenerationCount
effsReport[string(txType)] += rootCount
for _, effect := range effects[txType] {
diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go
index 29a2613d64..1ce40b0f13 100644
--- a/tools/block-generator/generator/generate_test.go
+++ b/tools/block-generator/generator/generate_test.go
@@ -674,16 +674,16 @@ func TestRecordData(t *testing.T) {
gen := makePrivateGenerator(t, 0, bookkeeping.Genesis{})
id := TxTypeID("test")
- data, ok := gen.reportData[id]
+ data, ok := gen.reportData.Transactions[id]
require.False(t, ok)
gen.recordData(id, time.Now())
- data, ok = gen.reportData[id]
+ data, ok = gen.reportData.Transactions[id]
require.True(t, ok)
require.Equal(t, uint64(1), data.GenerationCount)
gen.recordData(id, time.Now())
- data, ok = gen.reportData[id]
+ data, ok = gen.reportData.Transactions[id]
require.True(t, ok)
require.Equal(t, uint64(2), data.GenerationCount)
}
@@ -725,11 +725,13 @@ func TestCumulativeEffects(t *testing.T) {
partitiontest.PartitionTest(t)
report := Report{
- TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)},
- TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)},
- TxTypeID("pay_pay"): {GenerationCount: uint64(999)},
- TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)},
- TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)},
+ Transactions: map[TxTypeID]TxData{
+ TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)},
+ TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)},
+ TxTypeID("pay_pay"): {GenerationCount: uint64(999)},
+ TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)},
+ TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)},
+ },
}
expectedEffectsReport := EffectsReport{
diff --git a/tools/block-generator/generator/generator_ledger.go b/tools/block-generator/generator/generator_ledger.go
index 97fed9b344..dabad6ac21 100644
--- a/tools/block-generator/generator/generator_ledger.go
+++ b/tools/block-generator/generator/generator_ledger.go
@@ -20,6 +20,8 @@ import (
"encoding/binary"
"fmt"
"os"
+ "strings"
+ "time"
"github.com/algorand/avm-abi/apps"
cconfig "github.com/algorand/go-algorand/config"
@@ -167,19 +169,30 @@ func (g *generator) startEvaluator(hdr bookkeeping.BlockHeader, paysetHint int)
})
}
-func (g *generator) evaluateBlock(hdr bookkeeping.BlockHeader, txGroups [][]txn.SignedTxnWithAD, paysetHint int) (*ledgercore.ValidatedBlock, uint64 /* txnCount */, error) {
+func (g *generator) evaluateBlock(hdr bookkeeping.BlockHeader, txGroups [][]txn.SignedTxnWithAD, paysetHint int) (*ledgercore.ValidatedBlock, uint64 /* txnCount */, time.Duration /* commit wait time */, error) {
+ commitWaitTime := time.Duration(0)
+ waitDelay := 10 * time.Millisecond
eval, err := g.startEvaluator(hdr, paysetHint)
if err != nil {
- return nil, 0, fmt.Errorf("could not start evaluator: %w", err)
+ return nil, 0, 0, fmt.Errorf("could not start evaluator: %w", err)
}
for i, txGroup := range txGroups {
- err := eval.TransactionGroup(txGroup)
- if err != nil {
- return nil, 0, fmt.Errorf("could not evaluate transaction group %d: %w", i, err)
+ for {
+ err := eval.TransactionGroup(txGroup)
+ if err != nil {
+ if strings.Contains(err.Error(), "database table is locked") {
+ time.Sleep(waitDelay)
+ commitWaitTime += waitDelay
+ // sometimes the database is locked, so we retry
+ continue
+ }
+ return nil, 0, 0, fmt.Errorf("could not evaluate transaction group %d: %w", i, err)
+ }
+ break
}
}
lvb, err := eval.GenerateBlock()
- return lvb, eval.TestingTxnCounter(), err
+ return lvb, eval.TestingTxnCounter(), commitWaitTime, err
}
func countInners(ad txn.ApplyData) int {
diff --git a/tools/block-generator/generator/generator_types.go b/tools/block-generator/generator/generator_types.go
index 6685ffe7c8..622ddaf96d 100644
--- a/tools/block-generator/generator/generator_types.go
+++ b/tools/block-generator/generator/generator_types.go
@@ -149,7 +149,11 @@ type assetHolding struct {
}
// Report is the generation report.
-type Report map[TxTypeID]TxData
+type Report struct {
+ InitialRound uint64 `json:"initial_round"`
+ Counters map[string]uint64 `json:"counters"`
+ Transactions map[TxTypeID]TxData `json:"transactions"`
+}
// EffectsReport collates transaction counts caused by a root transaction.
type EffectsReport map[string]uint64
diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go
index edfe470f3d..5b170c504e 100644
--- a/tools/block-generator/generator/server.go
+++ b/tools/block-generator/generator/server.go
@@ -75,28 +75,30 @@ func help(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Use /v2/blocks/:blocknum: to get a block.")
}
-func maybeWriteError(w http.ResponseWriter, err error) {
+func maybeWriteError(handler string, w http.ResponseWriter, err error) {
if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
+ msg := fmt.Sprintf("%s handler: error encountered while writing response for: %v\n", handler, err)
+ fmt.Println(msg)
+ http.Error(w, msg, http.StatusInternalServerError)
return
}
}
func getReportHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
- maybeWriteError(w, gen.WriteReport(w))
+ maybeWriteError("report", w, gen.WriteReport(w))
}
}
func getStatusWaitHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
- maybeWriteError(w, gen.WriteStatus(w))
+ maybeWriteError("status wait", w, gen.WriteStatus(w))
}
}
func getGenesisHandler(gen Generator) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
- maybeWriteError(w, gen.WriteGenesis(w))
+ maybeWriteError("genesis", w, gen.WriteGenesis(w))
}
}
@@ -113,7 +115,7 @@ func getBlockHandler(gen Generator) func(w http.ResponseWriter, r *http.Request)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- maybeWriteError(w, gen.WriteBlock(w, round))
+ maybeWriteError("block", w, gen.WriteBlock(w, round))
}
}
@@ -125,7 +127,7 @@ func getAccountHandler(gen Generator) func(w http.ResponseWriter, r *http.Reques
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- maybeWriteError(w, gen.WriteAccount(w, account))
+ maybeWriteError("account", w, gen.WriteAccount(w, account))
}
}
@@ -141,7 +143,7 @@ func getDeltasHandler(gen Generator) func(w http.ResponseWriter, r *http.Request
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- maybeWriteError(w, gen.WriteDeltas(w, round))
+ maybeWriteError("deltas", w, gen.WriteDeltas(w, round))
}
}
diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod
index bc58c9a96b..965cbd9a09 100644
--- a/tools/block-generator/go.mod
+++ b/tools/block-generator/go.mod
@@ -8,7 +8,7 @@ require (
github.com/algorand/avm-abi v0.2.0
github.com/algorand/go-algorand v0.0.0
github.com/algorand/go-codec/codec v1.1.10
- github.com/algorand/go-deadlock v0.2.2
+ github.com/algorand/go-deadlock v0.2.3
github.com/lib/pq v1.10.9
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
@@ -19,12 +19,14 @@ require (
github.com/DataDog/zstd v1.5.2 // indirect
github.com/algorand/falcon v0.1.0 // indirect
github.com/algorand/go-sumhash v0.1.0 // indirect
- github.com/algorand/msgp v1.1.55 // indirect
+ github.com/algorand/msgp v1.1.60 // indirect
github.com/algorand/oapi-codegen v1.12.0-algorand.0 // indirect
github.com/algorand/sortition v1.0.0 // indirect
github.com/algorand/websocket v1.4.6 // indirect
- github.com/aws/aws-sdk-go v1.33.0 // indirect
+ github.com/aws/aws-sdk-go v1.34.0 // indirect
+ github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
+ github.com/bits-and-blooms/bitset v1.7.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.8.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect
@@ -32,27 +34,62 @@ require (
github.com/cockroachdb/redact v1.0.8 // indirect
github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 // indirect
- github.com/consensys/gnark-crypto v0.7.0 // indirect
+ github.com/consensys/bavard v0.1.13 // indirect
+ github.com/consensys/gnark-crypto v0.12.1 // indirect
+ github.com/containerd/cgroups v1.1.0 // indirect
+ github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/dchest/siphash v1.2.1 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
+ github.com/docker/go-units v0.5.0 // indirect
+ github.com/elastic/gosigar v0.14.2 // indirect
+ github.com/flynn/noise v1.0.0 // indirect
+ github.com/francoispqt/gojay v1.2.13 // indirect
+ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+ github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/gopacket v1.1.19 // indirect
+ github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
+ github.com/gorilla/websocket v1.5.0 // indirect
+ github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect
+ github.com/huin/goupnp v1.2.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
+ github.com/ipfs/go-log/v2 v2.5.1 // indirect
+ github.com/jackpal/go-nat-pmp v1.0.2 // indirect
+ github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jmespath/go-jmespath v0.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
- github.com/kr/pretty v0.2.1 // indirect
+ github.com/koron/go-ssdp v0.0.4 // indirect
+ github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
+ github.com/libp2p/go-buffer-pool v0.1.0 // indirect
+ github.com/libp2p/go-cidranger v1.1.0 // indirect
+ github.com/libp2p/go-flow-metrics v0.1.0 // indirect
+ github.com/libp2p/go-libp2p v0.29.1 // indirect
+ github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect
+ github.com/libp2p/go-libp2p-pubsub v0.9.3 // indirect
+ github.com/libp2p/go-msgio v0.3.0 // indirect
+ github.com/libp2p/go-nat v0.2.0 // indirect
+ github.com/libp2p/go-netroute v0.2.1 // indirect
+ github.com/libp2p/go-reuseport v0.3.0 // indirect
+ github.com/libp2p/go-yamux/v4 v4.0.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
- github.com/mattn/go-sqlite3 v1.10.0 // indirect
+ github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/miekg/dns v1.1.55 // indirect
+ github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
+ github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
@@ -60,10 +97,16 @@ require (
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.10.1 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
+ github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
+ github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
+ github.com/multiformats/go-multistream v0.4.1 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/olivere/elastic v6.2.14+incompatible // indirect
+ github.com/onsi/ginkgo/v2 v2.11.0 // indirect
+ github.com/opencontainers/runtime-spec v1.0.2 // indirect
+ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -71,16 +114,31 @@ require (
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
+ github.com/quic-go/qpack v0.4.0 // indirect
+ github.com/quic-go/qtls-go1-19 v0.3.3 // indirect
+ github.com/quic-go/qtls-go1-20 v0.2.3 // indirect
+ github.com/quic-go/quic-go v0.36.3 // indirect
+ github.com/quic-go/webtransport-go v0.5.3 // indirect
+ github.com/raulk/go-watchdog v1.3.0 // indirect
+ github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
- golang.org/x/crypto v0.11.0 // indirect
+ go.uber.org/atomic v1.11.0 // indirect
+ go.uber.org/dig v1.17.0 // indirect
+ go.uber.org/fx v1.20.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/zap v1.24.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/mod v0.12.0 // indirect
- golang.org/x/net v0.12.0 // indirect
- golang.org/x/sys v0.10.0 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sync v0.3.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.11.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
+ rsc.io/tmplfunc v0.0.3 // indirect
)
diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum
index 7b3b28e77e..dbba8cc727 100644
--- a/tools/block-generator/go.sum
+++ b/tools/block-generator/go.sum
@@ -1,5 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
@@ -30,7 +32,12 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
+dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
+dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
+git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -53,26 +60,35 @@ github.com/algorand/falcon v0.1.0 h1:xl832kfZ7hHG6B4p90DQynjfKFGbIUgUOnsRiMZXfAo
github.com/algorand/falcon v0.1.0/go.mod h1:OkQyHlGvS0kLNcIWbC21/uQcnbfwSOQm+wiqWwBG9pQ=
github.com/algorand/go-codec/codec v1.1.10 h1:zmWYU1cp64jQVTOG8Tw8wa+k0VfwgXIPbnDfiVa+5QA=
github.com/algorand/go-codec/codec v1.1.10/go.mod h1:YkEx5nmr/zuCeaDYOIhlDg92Lxju8tj2d2NrYqP7g7k=
-github.com/algorand/go-deadlock v0.2.2 h1:L7AKATSUCzoeVuOgpTipfCEjdUu5ECmlje8R7lP9DOY=
-github.com/algorand/go-deadlock v0.2.2/go.mod h1:Hat1OXKqKNUcN/iv74FjGhF4hsOE2l7gOgQ9ZVIq6Fk=
+github.com/algorand/go-deadlock v0.2.3 h1:ek9rjUyUF1HhUm0I2DyaCN8+3S850ONJNl5jQr9kZOA=
+github.com/algorand/go-deadlock v0.2.3/go.mod h1:Gli2d0Cb7kgXzSpJLC4Vn0DCLgjNVi6fNldY/mOtO/U=
github.com/algorand/go-sumhash v0.1.0 h1:b/QRhyLuF//vOcicBIxBXYW8bERNoeLxieht/dUYpVg=
github.com/algorand/go-sumhash v0.1.0/go.mod h1:OOe7jdDWUhLkuP1XytkK5gnLu9entAviN5DfDZh6XAc=
-github.com/algorand/msgp v1.1.55 h1:kWc9Xc08xtxCTWUiq1cRW5XGF+DFcfSGihYf0IZ/ivs=
-github.com/algorand/msgp v1.1.55/go.mod h1:RqZQBzAFDWpwh5TlabzZkWy+6kwL9cvXfLbU0gD99EA=
+github.com/algorand/msgp v1.1.60 h1:+IVUC34+tSj1P2M1mkYtl4GLyfzdzXfBLSw6TDT19M8=
+github.com/algorand/msgp v1.1.60/go.mod h1:RqZQBzAFDWpwh5TlabzZkWy+6kwL9cvXfLbU0gD99EA=
github.com/algorand/oapi-codegen v1.12.0-algorand.0 h1:W9PvED+wAJc+9EeXPONnA+0zE9UhynEqoDs4OgAxKhk=
github.com/algorand/oapi-codegen v1.12.0-algorand.0/go.mod h1:tIWJ9K/qrLDVDt5A1p82UmxZIEGxv2X+uoujdhEAL48=
github.com/algorand/sortition v1.0.0 h1:PJiZtdSTBm4nArQrZXBnhlljHXhuyAXRJBqVWowQu3E=
github.com/algorand/sortition v1.0.0/go.mod h1:23CZwAbTWPv0bBsq+Php/2J6Y/iXDyzlfcZyepeY5Fo=
github.com/algorand/websocket v1.4.6 h1:I0kV4EYwatuUrKtNiwzYYgojgwh6pksDmlqntKG2Woc=
github.com/algorand/websocket v1.4.6/go.mod h1:HJmdGzFtnlUQ4nTzZP6WrT29oGYf1t6Ybi64vROcT+M=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/aws/aws-sdk-go v1.33.0 h1:Bq5Y6VTLbfnJp1IV8EL/qUU5qO1DYHda/zis/sqevkY=
-github.com/aws/aws-sdk-go v1.33.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo=
+github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
+github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo=
+github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
+github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
+github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -82,6 +98,7 @@ github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz7
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4=
@@ -100,12 +117,23 @@ github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk
github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 h1:DJK8W/iB+s/qkTtmXSrHA49lp5O3OsR7E6z4byOLy34=
github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
-github.com/consensys/gnark-crypto v0.7.0 h1:rwdy8+ssmLYRqKp+ryRRgQJl/rCq2uv+n83cOydm5UE=
-github.com/consensys/gnark-crypto v0.7.0/go.mod h1:KPSuJzyxkJA8xZ/+CV47tyqkr9MmpZA3PXivK4VPrVg=
+github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
+github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
+github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
+github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
+github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
+github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
+github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -115,11 +143,20 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4=
github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
+github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
+github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/4=
+github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -128,12 +165,19 @@ github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHj
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
+github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
+github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
+github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@@ -148,12 +192,19 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -165,6 +216,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -172,6 +224,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -202,11 +256,15 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
+github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -216,21 +274,33 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
+github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
+github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU=
+github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
+github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
@@ -239,10 +309,19 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
+github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
+github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
+github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
+github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
+github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
+github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
+github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=
+github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -277,11 +356,15 @@ github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/q
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
+github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -290,25 +373,62 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
+github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
+github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
+github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=
+github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM=
+github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro=
+github.com/libp2p/go-libp2p v0.29.1 h1:yNeg6XgP8gbdc4YSrwiIt5T1TGOrVjH8dzl8h0GIOfQ=
+github.com/libp2p/go-libp2p v0.29.1/go.mod h1:20El+LLy3/YhdUYIvGbLnvVJN32nMdqY6KXBENRAfLY=
+github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s=
+github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w=
+github.com/libp2p/go-libp2p-pubsub v0.9.3 h1:ihcz9oIBMaCK9kcx+yHWm3mLAFBMAUsM4ux42aikDxo=
+github.com/libp2p/go-libp2p-pubsub v0.9.3/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhrdElWf8xtyli+Dzc=
+github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
+github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
+github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
+github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=
+github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=
+github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
+github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
+github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjPyvUw=
+github.com/libp2p/go-reuseport v0.3.0/go.mod h1:laea40AimhtfEqysZ71UpYj4S+R9VpH8PgqLo7L+SwI=
+github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ=
+github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4=
+github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
+github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
-github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
-github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
+github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
+github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
+github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
+github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
+github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=
+github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=
+github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
@@ -331,16 +451,23 @@ github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aG
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
+github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
github.com/multiformats/go-multiaddr v0.10.1 h1:HghtFrWyZEPrpTvgAMFJi6gFdgHfs2cb0pyfDsk+lqU=
github.com/multiformats/go-multiaddr v0.10.1/go.mod h1:jLEZsA61rwWNZQTHHnqq2HNa+4os/Hz54eqiRnsRqYQ=
github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
+github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
+github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
+github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
+github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
+github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo=
+github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
@@ -349,26 +476,37 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
+github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/olivere/elastic v6.2.14+incompatible h1:k+KadwNP/dkXE0/eu+T6otk1+5fe0tEpPyQJ4XVm5i8=
github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
+github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
+github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
+github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
+github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
+github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
+github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
@@ -382,12 +520,14 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
+github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@@ -395,20 +535,61 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
+github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
+github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
+github.com/quic-go/qtls-go1-19 v0.3.3 h1:wznEHvJwd+2X3PqftRha0SUKmGsnb6dfArMhy9PeJVE=
+github.com/quic-go/qtls-go1-19 v0.3.3/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
+github.com/quic-go/qtls-go1-20 v0.2.3 h1:m575dovXn1y2ATOb1XrRFcrv0F+EQmlowTkoraNkDPI=
+github.com/quic-go/qtls-go1-20 v0.2.3/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
+github.com/quic-go/quic-go v0.36.3 h1:f+yOqeGhMoRX7/M3wmEw/djhzKWr15FtQysox85/834=
+github.com/quic-go/quic-go v0.36.3/go.mod h1:qxQumdeKw5GmWs1OsTZZnOxzSI+RJWuhf1O8FN35L2o=
+github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU=
+github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU=
+github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
+github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
+github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
+github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
+github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
+github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
+github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
+github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
+github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
+github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
+github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
+github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
+github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
+github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
+github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
+github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
+github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
+github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
+github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
+github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@@ -428,15 +609,21 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
+github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
+github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@@ -449,15 +636,36 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI=
+go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU=
+go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ=
+go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0=
+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
+go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
+go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
+go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
+golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -465,9 +673,10 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
-golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -482,6 +691,7 @@ golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xpp
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -500,17 +710,21 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -537,20 +751,25 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -563,14 +782,18 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -590,6 +813,7 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -609,15 +833,19 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -627,11 +855,15 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -677,6 +909,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
@@ -684,6 +918,9 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
+google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -701,6 +938,8 @@ google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
@@ -708,6 +947,10 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
+google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -737,6 +980,9 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -767,11 +1013,13 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 h1:q/fZgS8MMadqFFGa8WL4Oyz+TmjiZfi8UrzWhTl8d5w=
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009/go.mod h1:O0bY1e/dSoxMYZYTHP0SWKxG5EWLEvKR9/cOjWPPMKU=
@@ -780,10 +1028,16 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -797,3 +1051,7 @@ pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
+rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
+sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
+sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
diff --git a/tools/block-generator/runner/reporting_test.go b/tools/block-generator/runner/reporting_test.go
new file mode 100644
index 0000000000..56d73b17a7
--- /dev/null
+++ b/tools/block-generator/runner/reporting_test.go
@@ -0,0 +1,100 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package runner
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/tools/block-generator/generator"
+)
+
+const initialRound = 1234
+const entryCount = 10
+
+func makeDummyData() (time.Time, time.Duration, generator.Report, *MetricsCollector) {
+ start := time.Now().Add(-10 * time.Minute)
+ duration := time.Hour
+ generatorReport := generator.Report{
+ InitialRound: initialRound,
+ Counters: make(map[string]uint64),
+ Transactions: make(map[generator.TxTypeID]generator.TxData),
+ }
+ collector := &MetricsCollector{Data: make([]Entry, entryCount)}
+ return start, duration, generatorReport, collector
+}
+
+// makeMetrics creates a set of metrics for testing.
+func makeMetrics(start time.Time) *MetricsCollector {
+ collector := &MetricsCollector{}
+ for i := 0; i <= entryCount; i++ {
+ var data []string
+
+ // should be converted to an average.
+ data = append(data, fmt.Sprintf("import_time_sec_sum %d", i*100))
+ data = append(data, fmt.Sprintf("import_time_sec_count %d", i))
+ // should be converted to an average.
+ data = append(data, fmt.Sprintf("imported_tx_per_block_sum %d", i*100))
+ data = append(data, fmt.Sprintf("imported_tx_per_block_count %d", i))
+
+ data = append(data, fmt.Sprintf("imported_round %d", i))
+ collector.Data = append(collector.Data, Entry{
+ Timestamp: start.Add(time.Duration(i) * time.Minute),
+ Data: data,
+ })
+ }
+ return collector
+}
+
+func TestWriteReport_MissingMetrics(t *testing.T) {
+ start, duration, generatorReport, collector := makeDummyData()
+ var builder strings.Builder
+ err := writeReport(&builder, t.Name(), start, duration, generatorReport, collector)
+ require.ErrorContains(t, err, "metric incomplete or not found")
+}
+
+func TestWriterReport_Good(t *testing.T) {
+ start, duration, generatorReport, _ := makeDummyData()
+ collector := makeMetrics(start)
+
+ generatorReport.Counters[generator.BlockTotalSizeBytes] = 1024
+ generatorReport.Counters[generator.BlockgenGenerateTimeMS] = 0
+ generatorReport.Counters[generator.CommitWaitTimeMS] = 1000
+ generatorReport.Counters[generator.LedgerEvalTimeMS] = 2000
+ generatorReport.Counters[generator.LedgerValidateTimeMS] = 3000
+
+ var builder strings.Builder
+ err := writeReport(&builder, t.Name(), start, duration, generatorReport, collector)
+ require.NoError(t, err)
+
+ report := builder.String()
+
+ // both rounds of metrics are reported.
+ assert.Contains(t, report, fmt.Sprintf("initial_round:%d", initialRound))
+ assert.Contains(t, report, fmt.Sprintf("final_imported_round:%d", entryCount))
+ assert.Contains(t, report, fmt.Sprintf("early_imported_round:%d", entryCount/5))
+
+ // counters are reported.
+ for k, v := range generatorReport.Counters {
+ assert.Contains(t, report, fmt.Sprintf("%s:%d", k, v))
+ }
+}
diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go
index 3b22315292..f2e1c1fc1f 100644
--- a/tools/block-generator/runner/run.go
+++ b/tools/block-generator/runner/run.go
@@ -19,11 +19,12 @@ package runner
import (
"bytes"
"context"
+ "encoding/json"
+ "io"
"sort"
// embed conduit template config file
_ "embed"
- "encoding/json"
"fmt"
"net/http"
"os"
@@ -42,8 +43,11 @@ import (
"github.com/algorand/go-algorand/tools/block-generator/util"
)
-//go:embed template/conduit.yml.tmpl
-var conduitConfigTmpl string
+//go:embed template/conduit_pg_exporter.tmpl
+var conduitPostgresConfigTmpl string
+
+//go:embed template/conduit_file_exporter.tmpl
+var conduitFileExporterConfigTmpl string
const pad = " "
@@ -53,6 +57,7 @@ type Args struct {
Path string
ConduitBinary string
MetricsPort uint64
+ Template string
PostgresConnectionString string
CPUProfilePath string
RunDuration time.Duration
@@ -64,6 +69,7 @@ type Args struct {
KeepDataDir bool
GenesisFile string
ResetDB bool
+ StartDelay time.Duration
Times uint64
}
@@ -124,6 +130,7 @@ func Run(args Args) error {
}
func (r *Args) run(reportDirectory string) error {
+
baseName := filepath.Base(r.Path)
baseNameNoExt := strings.TrimSuffix(baseName, filepath.Ext(baseName))
reportfile := path.Join(reportDirectory, fmt.Sprintf("%s.report", baseNameNoExt))
@@ -150,21 +157,36 @@ func (r *Args) run(reportDirectory string) error {
// get next db round
var nextRound uint64
var err error
- if r.ResetDB {
- fmt.Printf("%sPostgreSQL resetting.\n", pad)
- if err = util.EmptyDB(r.PostgresConnectionString); err != nil {
- return fmt.Errorf("emptyDB err: %w", err)
- }
- nextRound = 0
- } else {
- nextRound, err = util.GetNextRound(r.PostgresConnectionString)
- if err != nil && err == util.ErrorNotInitialized {
+ switch r.Template {
+ case "file-exporter":
+ fmt.Printf("%sUsing File Exporter to persist blocks.\n", pad)
+ case "postgres-exporter":
+ fmt.Printf("%sUsing PostgreSQL Exporter to persist blocks.\n", pad)
+ if r.ResetDB {
+ fmt.Printf("%sPostgreSQL resetting.\n", pad)
+ if err = util.EmptyDB(r.PostgresConnectionString); err != nil {
+ return fmt.Errorf("emptyDB err: %w", err)
+ }
nextRound = 0
- } else if err != nil {
- return fmt.Errorf("getNextRound err: %w", err)
+ } else {
+ nextRound, err = util.GetNextRound(r.PostgresConnectionString)
+ if err != nil && err == util.ErrorNotInitialized {
+ nextRound = 0
+ } else if err != nil {
+ return fmt.Errorf("getNextRound err: %w", err)
+ }
+ fmt.Printf("%sPostgreSQL next round: %d\n", pad, nextRound)
}
- fmt.Printf("%sPostgreSQL next round: %d\n", pad, nextRound)
+ default:
+ // TODO: the default case should attempt to read the supplied template name as a file under ./template/
+ return fmt.Errorf("unknown template type: %s", r.Template)
}
+
+ if r.StartDelay > 0 {
+ fmt.Printf("%sSleeping for start delay: %s\n", pad, r.StartDelay)
+ time.Sleep(r.StartDelay)
+ }
+
// Start services
algodNet := fmt.Sprintf("localhost:%d", 11112)
metricsNet := fmt.Sprintf("localhost:%d", r.MetricsPort)
@@ -179,6 +201,16 @@ func (r *Args) run(reportDirectory string) error {
}()
// create conduit config from template
+ var conduitConfigTmpl string
+ switch r.Template {
+ case "file-exporter":
+ conduitConfigTmpl = conduitFileExporterConfigTmpl
+ case "postgres-exporter":
+ conduitConfigTmpl = conduitPostgresConfigTmpl
+ default:
+ return fmt.Errorf("unknown template type: %s", r.Template)
+ }
+
t, err := template.New("conduit").Parse(conduitConfigTmpl)
if err != nil {
return fmt.Errorf("unable to parse conduit config template: %w", err)
@@ -253,12 +285,12 @@ const (
)
// Helper to record metrics. Supports rates (sum/count) and counters.
-func recordDataToFile(start time.Time, entry Entry, prefix string, out *os.File) error {
+func recordDataToWriter(start time.Time, entry Entry, prefix string, out io.Writer) error {
var writeErrors []string
var writeErr error
record := func(prefix2, name string, t metricType) {
key := fmt.Sprintf("%s%s_%s", prefix, prefix2, name)
- if err := recordMetricToFile(entry, key, name, t, out); err != nil {
+ if err := recordMetricToWriter(entry, key, name, t, out); err != nil {
writeErr = err
writeErrors = append(writeErrors, name)
}
@@ -287,21 +319,21 @@ func recordDataToFile(start time.Time, entry Entry, prefix string, out *os.File)
tps := totalTxn / importTimeS
key := "overall_transactions_per_second"
msg := fmt.Sprintf("%s_%s:%.2f\n", prefix, key, tps)
- if _, err := out.WriteString(msg); err != nil {
+ if _, err := fmt.Fprint(out, msg); err != nil {
return fmt.Errorf("unable to write metric '%s': %w", key, err)
}
// Uptime
key = "uptime_seconds"
msg = fmt.Sprintf("%s_%s:%.2f\n", prefix, key, time.Since(start).Seconds())
- if _, err := out.WriteString(msg); err != nil {
+ if _, err := fmt.Fprint(out, msg); err != nil {
return fmt.Errorf("unable to write metric '%s': %w", key, err)
}
return nil
}
-func recordMetricToFile(entry Entry, outputKey, metricSuffix string, t metricType, out *os.File) error {
+func recordMetricToWriter(entry Entry, outputKey, metricSuffix string, t metricType, out io.Writer) error {
value, err := getMetric(entry, metricSuffix, t == rate)
if err != nil {
return err
@@ -314,7 +346,7 @@ func recordMetricToFile(entry Entry, outputKey, metricSuffix string, t metricTyp
msg = fmt.Sprintf("%s:%.2f\n", outputKey, value)
}
- if _, err := out.WriteString(msg); err != nil {
+ if _, err := fmt.Fprint(out, msg); err != nil {
return fmt.Errorf("unable to write metric '%s': %w", outputKey, err)
}
@@ -371,50 +403,35 @@ func getMetric(entry Entry, suffix string, rateMetric bool) (float64, error) {
return 0.0, fmt.Errorf("metric incomplete or not found: %s", suffix)
}
-// Run the test for 'RunDuration', collect metrics and write them to the 'ReportDirectory'
-func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) error {
- collector := &MetricsCollector{MetricsURL: fmt.Sprintf("http://%s/metrics", metricsURL)}
-
- // Run for r.RunDuration
- start := time.Now()
- fmt.Printf("%sduration starting now: %s\n", pad, start)
- count := 1
- for time.Since(start) < r.RunDuration {
- time.Sleep(r.RunDuration / 10)
- fmt.Printf("%scollecting metrics (%d)\n", pad, count)
-
- if err := collector.Collect(AllMetricNames...); err != nil {
- return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err)
+func writeReport(w io.Writer, scenario string, start time.Time, runDuration time.Duration, generatorReport generator.Report, collector *MetricsCollector) error {
+ write := func(pattern string, parts ...any) error {
+ str := fmt.Sprintf(pattern, parts...)
+ if _, err := fmt.Fprint(w, str); err != nil {
+ return fmt.Errorf("unable to write '%s': %w", str, err)
}
- count++
+ return nil
}
- fmt.Printf("%scollecting final metrics\n", pad)
- if err := collector.Collect(AllMetricNames...); err != nil {
- return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err)
+ if err := write("scenario:%s\n", scenario); err != nil {
+ return err
}
- // write scenario to report
- scenario := path.Base(r.Path)
- if _, err := report.WriteString(fmt.Sprintf("scenario:%s\n", scenario)); err != nil {
- return fmt.Errorf("unable to write scenario to report: %w", err)
+ if err := write("test_duration_seconds:%d\n", uint64(runDuration.Seconds())); err != nil {
+ return err
}
- // Collect results.
- durationStr := fmt.Sprintf("test_duration_seconds:%d\ntest_duration_actual_seconds:%f\n",
- uint64(r.RunDuration.Seconds()),
- time.Since(start).Seconds())
- if _, err := report.WriteString(durationStr); err != nil {
- return fmt.Errorf("unable to write duration metric: %w", err)
+
+ if err := write("test_duration_actual_seconds:%f\n", time.Since(start).Seconds()); err != nil {
+ return err
}
- resp, err := http.Get(fmt.Sprintf("http://%s/report", generatorURL))
- if err != nil {
- return fmt.Errorf("generator report query failed")
+ if err := write("initial_round:%d\n", generatorReport.InitialRound); err != nil {
+ return err
}
- defer resp.Body.Close()
- var generatorReport generator.Report
- if err = json.NewDecoder(resp.Body).Decode(&generatorReport); err != nil {
- return fmt.Errorf("problem decoding generator report: %w", err)
+
+ for metric, value := range generatorReport.Counters {
+ if err := write("%s:%d\n", metric, value); err != nil {
+ return err
+ }
}
effects := generator.CumulativeEffects(generatorReport)
@@ -432,36 +449,79 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string)
txCount := effects[metric]
allTxns += txCount
str := fmt.Sprintf("transaction_%s_total:%d\n", metric, txCount)
- if _, err = report.WriteString(str); err != nil {
- return fmt.Errorf("unable to write transaction_count metric: %w", err)
+ if _, err := fmt.Fprint(w, str); err != nil {
+ return fmt.Errorf("unable to write '%s' metric: %w", str, err)
}
}
str := fmt.Sprintf("transaction_%s_total:%d\n", "ALL", allTxns)
- if _, err = report.WriteString(str); err != nil {
- return fmt.Errorf("unable to write transaction_count metric: %w", err)
+ if _, err := fmt.Fprint(w, str); err != nil {
+ return fmt.Errorf("unable to write '%s' metric: %w", str, err)
}
// Record a rate from one of the first data points.
if len(collector.Data) > 5 {
- if err = recordDataToFile(start, collector.Data[2], "early", report); err != nil {
+ if err := recordDataToWriter(start, collector.Data[2], "early", w); err != nil {
return err
}
}
// Also record the final metrics.
- if err = recordDataToFile(start, collector.Data[len(collector.Data)-1], "final", report); err != nil {
+ if err := recordDataToWriter(start, collector.Data[len(collector.Data)-1], "final", w); err != nil {
return err
}
return nil
}
+// Run the test for 'RunDuration', collect metrics and write report to the report file.
+func (r *Args) runTest(w io.Writer, metricsURL string, generatorURL string) error {
+ collector := &MetricsCollector{MetricsURL: fmt.Sprintf("http://%s/metrics", metricsURL)}
+
+ // Run for r.RunDuration
+ start := time.Now()
+ fmt.Printf("%sduration starting now: %s\n", pad, start)
+ count := 1
+ for time.Since(start) < r.RunDuration {
+ time.Sleep(r.RunDuration / 10)
+ fmt.Printf("%scollecting metrics (%d)\n", pad, count)
+
+ if err := collector.Collect(AllMetricNames...); err != nil {
+ return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err)
+ }
+ count++
+ }
+
+ fmt.Printf("%scollecting final metrics\n", pad)
+ if err := collector.Collect(AllMetricNames...); err != nil {
+ return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err)
+ }
+
+ // get generator report
+ scenario := path.Base(r.Path)
+ resp, err := http.Get(fmt.Sprintf("http://%s/report", generatorURL))
+ if err != nil {
+ return fmt.Errorf("generator report query failed")
+ }
+ defer resp.Body.Close()
+ var generatorReport generator.Report
+ if err = json.NewDecoder(resp.Body).Decode(&generatorReport); err != nil {
+ return fmt.Errorf("problem decoding generator report: %w", err)
+ }
+
+ // write report to file
+ err = writeReport(w, scenario, start, r.RunDuration, generatorReport, collector)
+ if err != nil {
+ return fmt.Errorf("problem writing report: %w", err)
+ }
+ return nil
+}
+
// startGenerator starts the generator server.
func startGenerator(ledgerLogFile, configFile string, dbround uint64, genesisFile string, verbose bool, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) {
f, err := os.OpenFile(ledgerLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
util.MaybeFail(err, "unable to open ledger log file '%s'", ledgerLogFile)
log := logging.NewLogger()
- log.SetLevel(logging.Warn)
+ log.SetLevel(logging.Info)
log.SetOutput(f)
// Start generator.
diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go
index ff2b49948d..4c11fde59c 100644
--- a/tools/block-generator/runner/runner.go
+++ b/tools/block-generator/runner/runner.go
@@ -35,17 +35,21 @@ func init() {
Use: "runner",
Short: "Run test suite and collect results.",
Long: "Run an automated test suite using the block-generator daemon and a provided conduit binary. Results are captured to a specified output directory.",
- Run: func(cmd *cobra.Command, args []string) {
+ RunE: func(cmd *cobra.Command, args []string) error{
fmt.Printf("starting block-generator runner with args: %+v\n", runnerArgs)
- if err := Run(runnerArgs); err != nil {
- fmt.Println(err)
+
+ if runnerArgs.Template == "postgres-exporter" && runnerArgs.PostgresConnectionString == "" {
+ return fmt.Errorf("exporting to postgres requires that postgres-connection-string to be set")
}
+
+ return Run(runnerArgs)
},
}
RunnerCmd.Flags().StringVarP(&runnerArgs.Path, "scenario", "s", "", "Directory containing scenarios, or specific scenario file.")
RunnerCmd.Flags().StringVarP(&runnerArgs.ConduitBinary, "conduit-binary", "i", "", "Path to conduit binary.")
RunnerCmd.Flags().Uint64VarP(&runnerArgs.MetricsPort, "metrics-port", "p", 9999, "Port to start the metrics server at.")
+ RunnerCmd.Flags().StringVarP(&runnerArgs.Template, "template", "", "postgres-exporter", "Specify the conduit template to use. Choices are: file-exporter or postgres-exporter.")
RunnerCmd.Flags().StringVarP(&runnerArgs.PostgresConnectionString, "postgres-connection-string", "c", "", "Postgres connection string.")
RunnerCmd.Flags().DurationVarP(&runnerArgs.RunDuration, "test-duration", "d", 5*time.Minute, "Duration to use for each scenario.")
RunnerCmd.Flags().StringVarP(&runnerArgs.BaseReportDirectory, "report-directory", "r", "", "Location to place test reports. If --times is used, this is the prefix for multiple report directories.")
@@ -58,9 +62,9 @@ func init() {
RunnerCmd.Flags().StringVarP(&runnerArgs.GenesisFile, "genesis-file", "f", "", "file path to the genesis associated with the db snapshot")
RunnerCmd.Flags().BoolVarP(&runnerArgs.ResetDB, "reset-db", "", false, "If set database will be deleted before running tests.")
RunnerCmd.Flags().Uint64VarP(&runnerArgs.Times, "times", "t", 1, "Number of times to run the scenario(s).")
+ RunnerCmd.Flags().DurationVarP(&runnerArgs.StartDelay, "start-delay", "", 0, "Duration to wait before starting a test scenario. This may be useful for snapshot tests where DB maintenance occurs after loading data.")
RunnerCmd.MarkFlagRequired("scenario")
RunnerCmd.MarkFlagRequired("conduit-binary")
- RunnerCmd.MarkFlagRequired("postgres-connection-string")
RunnerCmd.MarkFlagRequired("report-directory")
}
diff --git a/tools/block-generator/runner/template/conduit_file_exporter.tmpl b/tools/block-generator/runner/template/conduit_file_exporter.tmpl
new file mode 100644
index 0000000000..f675022096
--- /dev/null
+++ b/tools/block-generator/runner/template/conduit_file_exporter.tmpl
@@ -0,0 +1,65 @@
+# Log verbosity: PANIC, FATAL, ERROR, WARN, INFO, DEBUG, TRACE
+log-level: {{.LogLevel}}
+
+# If no log file is provided logs are written to stdout.
+log-file: {{.LogFile}}
+
+# Number of retries to perform after a pipeline plugin error.
+retry-count: 120
+
+# Time duration to wait between retry attempts.
+retry-delay: "1s"
+
+# Optional filepath to use for pidfile.
+#pid-filepath: /path/to/pidfile
+
+# Whether or not to print the conduit banner on startup.
+hide-banner: false
+
+# When enabled prometheus metrics are available on '/metrics'
+metrics:
+ mode: ON
+ addr: "{{.MetricsPort}}"
+ prefix: "conduit"
+
+# The importer is typically an algod follower node.
+importer:
+ name: algod
+ config:
+ # The mode of operation, either "archival" or "follower".
+ # * archival mode allows you to start processing on any round but does not
+ # contain the ledger state delta objects required for the postgres writer.
+ # * follower mode allows you to use a lightweight non-archival node as the
+ # data source. In addition, it will provide ledger state delta objects to
+ # the processors and exporter.
+ mode: "follower"
+
+ # Algod API address.
+ netaddr: "{{.AlgodNet}}"
+
+ # Algod API token.
+ token: ""
+
+
+# Zero or more processors may be defined to manipulate what data
+# reaches the exporter.
+processors:
+
+# An exporter is defined to do something with the data.
+exporter:
+ name: "file_writer"
+ config:
+ # BlocksDir is the path to a directory where block data should be stored.
+ # The directory is created if it doesn't exist. If no directory is provided
+ # blocks are written to the Conduit data directory.
+ #block-dir: "/path/to/block/files"
+
+ # FilenamePattern is the format used to write block files. It uses go
+ # string formatting and should accept one number for the round.
+ # If the file has a '.gz' extension, blocks will be gzipped.
+ # Default: "%[1]d_block.msgp.gz"
+ filename-pattern: "%[1]d_block.msgp.gz"
+
+ # DropCertificate is used to remove the vote certificate from the block data before writing files.
+ drop-certificate: false
+
diff --git a/tools/block-generator/runner/template/conduit.yml.tmpl b/tools/block-generator/runner/template/conduit_pg_exporter.tmpl
similarity index 100%
rename from tools/block-generator/runner/template/conduit.yml.tmpl
rename to tools/block-generator/runner/template/conduit_pg_exporter.tmpl
diff --git a/tools/block-generator/scripts/run_runner.py b/tools/block-generator/scripts/run_runner.py
deleted file mode 100644
index 5f0753930b..0000000000
--- a/tools/block-generator/scripts/run_runner.py
+++ /dev/null
@@ -1,201 +0,0 @@
-import argparse
-import os
-from pathlib import Path
-import shlex
-import subprocess
-import sys
-import time
-
-
-POSTGRES_CONTAINER = "generator-test-container"
-POSTGRES_PORT = 15432
-POSTGRES_DATABASE = "generator_db"
-
-REPORT_DIRECTORY = "../../tmp/OUTPUT_RUN_RUNNER_TEST"
-
-CWD = Path.cwd()
-
-NL = "\n"
-BS = "\\"
-DBS = BS * 2
-Q = '"'
-SQ = ' "'
-
-
-def run_cmd(cmd):
- print(f"Running command: {cmd}")
- process = subprocess.Popen(
- shlex.split(cmd.replace("\\\n", " ")),
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- stdout, stderr = process.communicate()
- if (rcode := process.returncode) != 0:
- print(f"Error executing command: {cmd}")
- print(stderr.decode())
- sys.exit(rcode)
- return stdout.decode()
-
-
-def up(args):
- run_cmd(f"docker rm -f {args.pg_container}")
- run_cmd(
- f"docker run -d --name {args.pg_container} -e POSTGRES_USER=algorand -e POSTGRES_PASSWORD=algorand -p {args.pg_port}:5432 postgres"
- )
- time.sleep(5)
-
- run_cmd(
- f'docker exec -it {args.pg_container} psql -Ualgorand -c "create database {args.pg_database}"'
- )
-
-
-def down(args):
- run_cmd(f"docker rm -f {args.pg_container}")
-
-
-def launch_json_args(cmd: str):
- def tighten(x):
- return x.replace(" \\", "\\")
-
- def wrap(x):
- return tighten(x) if x.startswith('"') else f'"{tighten(x)}"'
-
- newlines = []
- lines = cmd.splitlines()
- for i, line in enumerate(lines):
- if i == 0:
- continue
- if not line.startswith("--"):
- aline = wrap(line.replace(" ", ""))
- else:
- aline = ", ".join(map(wrap, line.split(" ", maxsplit=1)))
-
- if i < len(lines) - 1:
- aline += ","
-
- newlines.append(aline)
- return f"[{(NL.join(newlines)).replace(BS, '')}]"
-
-
-def parse_args():
- parser = argparse.ArgumentParser()
- parser.add_argument("--conduit-binary", help="Path to conduit binary")
- parser.add_argument(
- "--scenario",
- default=(default := CWD.parents[1] / "test_scenario.yml"),
- help=f"Scenario configuration file ({default=!s})",
- )
- parser.add_argument(
- "--reset-db",
- action="store_true",
- default=False,
- help="Reset the DB and start at round 0 (default=False)",
- )
- parser.add_argument(
- "--purge",
- action="store_true",
- default=False,
- help="Shutdown container that has been kept alive (default=False)",
- )
- parser.add_argument(
- "--keep-alive",
- action="store_true",
- default=False,
- help="Keep postgres container alive at end of run (default=False)",
- )
- parser.add_argument(
- "--pg-container",
- default=(default := POSTGRES_CONTAINER),
- help=f"Name of postgres container ({default=})",
- )
- parser.add_argument(
- "--pg-port",
- default=(default := POSTGRES_PORT),
- help=f"Postgres port ({default=})",
- )
- parser.add_argument(
- "--pg-database",
- default=(default := POSTGRES_DATABASE),
- help=f"Postgres database ({default=})",
- )
- parser.add_argument(
- "--report-directory",
- default=(default := REPORT_DIRECTORY),
- help=f"Report directory ({default=})",
- )
- parser.add_argument(
- "--build-generator",
- action="store_true",
- default=False,
- help="Build the generator binary (default=False)",
- )
- parser.add_argument(
- "--skip-runner",
- action="store_true",
- default=False,
- help="Skip running the generator (default=False)",
- )
- parser.add_argument(
- "--test-duration",
- default=(default := "30s"),
- help=f"Test duration ({default=})",
- )
-
- args = parser.parse_args()
- print(args)
- return args
-
-
-def main():
- args = parse_args()
-
- try:
- if not args.purge:
- print(f"Using scenario file: {args.scenario}")
- print(f"!!! rm -rf {args.report_directory} !!!")
- run_cmd(f"rm -rf {args.report_directory}")
-
- if args.build_generator:
- print("Building generator.")
- os.chdir(CWD)
- run_cmd("go build")
- os.chdir("..")
- else:
- print("Skipping generator build.")
-
- print("Starting postgres container.")
- up(args)
-
- SLNL = "\\\n"
- generator_cmd = f"""{CWD}/block-generator \\
-runner \\
---conduit-binary "{args.conduit_binary}" \\
---report-directory {args.report_directory} \\
---test-duration {args.test_duration} \\
---conduit-log-level trace \\
---postgres-connection-string "host=localhost user=algorand password=algorand dbname={args.pg_database} port={args.pg_port} sslmode=disable" \\
---scenario {args.scenario} {DBS + NL + '--reset-db' if args.reset_db else ''}"""
- if args.skip_runner:
- print("Skipping test runner.")
- print(f"Run it yourself:\n{generator_cmd}")
- print(
- f"""`launch.json` args:
-{launch_json_args(generator_cmd)}"""
- )
- else:
- print("Starting test runner")
- run_cmd(generator_cmd)
- else:
- print("Purging postgres container - NO OTHER ACTION TAKEN")
- down(args)
- finally:
- if not args.keep_alive:
- print("Stopping postgres container.")
- down(args)
- else:
- print(f"Keeping postgres container alive: {args.pg_container}")
- print(f"Also, not removing report directory: {args.report_directory}")
-
-
-if __name__ == "__main__":
- main()
diff --git a/tools/debug/transplanter/main.go b/tools/debug/transplanter/main.go
index 1c7a3a6b41..1ee2a9c84e 100644
--- a/tools/debug/transplanter/main.go
+++ b/tools/debug/transplanter/main.go
@@ -381,7 +381,6 @@ func main() {
l.Close()
fmt.Printf("Catching up from %d to %d\n", latest, *roundStart)
-
followerNode, err = node.MakeFollower(log, rootPath, cfg, []string{}, genesis)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot init follower node: %v", err)
diff --git a/tools/network/dnssec/config_windows.go b/tools/network/dnssec/config_windows.go
index 36c0aaf377..318f8f7a49 100644
--- a/tools/network/dnssec/config_windows.go
+++ b/tools/network/dnssec/config_windows.go
@@ -21,7 +21,6 @@ package dnssec
import (
"fmt"
- "runtime/debug"
"time"
"unsafe"
@@ -94,25 +93,31 @@ type fixedInfoWithOverlay struct {
// See GetNetworkParams for details:
// https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getnetworkparams
func SystemConfig() (servers []ResolverAddress, timeout time.Duration, err error) {
- // disable GC to prevent fi collection earlier than lookups in fi completed
- pct := debug.SetGCPercent(-1)
- defer debug.SetGCPercent(pct)
+ ulSize := uint32(unsafe.Sizeof(fixedInfoWithOverlay{}))
+
+ buf, err := windows.LocalAlloc(windows.LMEM_FIXED|windows.LMEM_ZEROINIT, ulSize)
+ if err != nil {
+ err = fmt.Errorf("GetNetworkParams failed to allocate %d bytes of memory for fixedInfoWithOverlay", ulSize)
+ return
+ }
+
+ defer windows.LocalFree(windows.Handle(buf))
- var fi fixedInfoWithOverlay
- var ulSize uint32 = uint32(unsafe.Sizeof(fi))
ret, _, _ := networkParamsProc.Call(
- uintptr(unsafe.Pointer(&fi)),
+ buf,
uintptr(unsafe.Pointer(&ulSize)),
)
if ret != 0 {
if windows.Errno(ret) == windows.ERROR_BUFFER_OVERFLOW {
- err = fmt.Errorf("GetNetworkParams requested %d bytes of memory, max supported is %d. Error code is %x", ulSize, unsafe.Sizeof(fi), ret)
+ err = fmt.Errorf("GetNetworkParams requested %d bytes of memory, max supported is %d. Error code is %x", ulSize, unsafe.Sizeof(fixedInfoWithOverlay{}), ret)
return
}
err = fmt.Errorf("GetNetworkParams failed with code is %x", ret)
return
}
+ fi := (*fixedInfoWithOverlay)(unsafe.Pointer(buf))
+
var p *ipAddrString = &fi.DnsServerList
for {
ip := make([]byte, ip_size)
diff --git a/tools/x-repo-types/go.sum b/tools/x-repo-types/go.sum
index 7929646e02..7bc3b497d6 100644
--- a/tools/x-repo-types/go.sum
+++ b/tools/x-repo-types/go.sum
@@ -3,8 +3,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -14,7 +12,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/util/codecs/json.go b/util/codecs/json.go
index e283ef0624..8c2cebf087 100644
--- a/util/codecs/json.go
+++ b/util/codecs/json.go
@@ -18,6 +18,7 @@ package codecs
import (
"bufio"
+ "bytes"
"encoding/json"
"fmt"
"io"
@@ -48,6 +49,16 @@ func LoadObjectFromFile(filename string, object interface{}) (err error) {
return
}
+func writeBytes(writer io.Writer, object interface{}, prettyFormat bool) error {
+ var enc *json.Encoder
+ if prettyFormat {
+ enc = NewFormattedJSONEncoder(writer)
+ } else {
+ enc = json.NewEncoder(writer)
+ }
+ return enc.Encode(object)
+}
+
// SaveObjectToFile implements the common pattern for saving an object to a file as json
func SaveObjectToFile(filename string, object interface{}, prettyFormat bool) error {
f, err := os.Create(filename)
@@ -55,22 +66,13 @@ func SaveObjectToFile(filename string, object interface{}, prettyFormat bool) er
return err
}
defer f.Close()
- var enc *json.Encoder
- if prettyFormat {
- enc = NewFormattedJSONEncoder(f)
- } else {
- enc = json.NewEncoder(f)
- }
- err = enc.Encode(object)
- return err
+ return writeBytes(f, object, prettyFormat)
}
-// SaveNonDefaultValuesToFile saves an object to a file as json, but only fields that are not
+// WriteNonDefaultValues writes object to a writer as json, but only fields that are not
// currently set to be the default value.
// Optionally, you can specify an array of field names to always include.
-func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface{}, ignore []string, prettyFormat bool) error {
- // Serialize object to temporary file.
- // Read file into string array
+func WriteNonDefaultValues(writer io.Writer, object, defaultObject interface{}, ignore []string) error {
// Iterate one line at a time, parse Name
// If ignore contains Name, don't delete
// Use reflection to compare object[Name].value == defaultObject[Name].value
@@ -78,25 +80,13 @@ func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface
// When done, ensure last value line doesn't include comma
// Write string array to file.
- file, err := os.CreateTemp("", "encsndv")
- if err != nil {
- return err
- }
- name := file.Name()
- file.Close()
-
- defer os.Remove(name)
- // Save object to file pretty-formatted so we can read one value-per-line
- err = SaveObjectToFile(name, object, true)
+ var buf bytes.Buffer
+ err := writeBytes(&buf, object, true)
if err != nil {
return err
}
+ content := buf.Bytes()
- // Read lines from encoded file into string array
- content, err := os.ReadFile(name)
- if err != nil {
- return err
- }
valueLines := strings.Split(string(content), "\n")
// Create maps of the name->value pairs for the object and the defaults
@@ -155,19 +145,30 @@ func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface
}
}
+ combined := strings.Join(newFile, "\n")
+ combined = strings.TrimRight(combined, "\r\n ")
+ _, err = writer.Write([]byte(combined))
+ return err
+}
+
+// SaveNonDefaultValuesToFile saves an object to a file as json, but only fields that are not
+// currently set to be the default value.
+// Optionally, you can specify an array of field names to always include.
+func SaveNonDefaultValuesToFile(filename string, object, defaultObject interface{}, ignore []string) error {
outFile, err := os.Create(filename)
if err != nil {
return err
}
defer outFile.Close()
writer := bufio.NewWriter(outFile)
- combined := strings.Join(newFile, "\n")
- combined = strings.TrimRight(combined, "\r\n ")
- _, err = writer.WriteString(combined)
- if err == nil {
- writer.Flush()
+
+ err = WriteNonDefaultValues(writer, object, defaultObject, ignore)
+ if err != nil {
+ return err
}
- return err
+
+ writer.Flush()
+ return nil
}
func extractValueName(line string) (name string) {
diff --git a/util/codecs/json_test.go b/util/codecs/json_test.go
index 1f56531971..6bd4d53cd0 100644
--- a/util/codecs/json_test.go
+++ b/util/codecs/json_test.go
@@ -17,9 +17,15 @@
package codecs
import (
- "github.com/algorand/go-algorand/test/partitiontest"
- "github.com/stretchr/testify/require"
+ "bytes"
+ "os"
+ "path"
"testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
)
type testValue struct {
@@ -30,6 +36,7 @@ type testValue struct {
func TestIsDefaultValue(t *testing.T) {
partitiontest.PartitionTest(t)
+ t.Parallel()
a := require.New(t)
@@ -52,3 +59,113 @@ func TestIsDefaultValue(t *testing.T) {
a.False(isDefaultValue("Int", objectValues, defaultValues))
a.True(isDefaultValue("Missing", objectValues, defaultValues))
}
+
+func TestSaveObjectToFile(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ type TestType struct {
+ A uint64
+ B string
+ }
+
+ obj := TestType{1024, "test"}
+
+ // prettyFormat = false
+ {
+ filename := path.Join(t.TempDir(), "test.json")
+ SaveObjectToFile(filename, obj, false)
+ data, err := os.ReadFile(filename)
+ require.NoError(t, err)
+ expected := `{"A":1024,"B":"test"}
+`
+ require.Equal(t, expected, string(data))
+ }
+
+ // prettyFormat = true
+ {
+ filename := path.Join(t.TempDir(), "test.json")
+ SaveObjectToFile(filename, obj, true)
+ data, err := os.ReadFile(filename)
+ require.NoError(t, err)
+ expected := `{
+ "A": 1024,
+ "B": "test"
+}
+`
+ require.Equal(t, expected, string(data))
+ }
+
+}
+
+func TestWriteNonDefaultValue(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ type TestType struct {
+ Version uint32
+ Archival bool
+ GossipFanout int
+ NetAddress string
+ ReconnectTime time.Duration
+ }
+
+ defaultObject := TestType{
+ Version: 1,
+ Archival: true,
+ GossipFanout: 50,
+ NetAddress: "Denver",
+ ReconnectTime: 60 * time.Second,
+ }
+
+ testcases := []struct {
+ name string
+ in TestType
+ out string
+ ignore []string
+ }{
+ {
+ name: "all defaults",
+ in: defaultObject,
+ out: `{
+}`,
+ }, {
+ name: "some defaults",
+ in: TestType{
+ Version: 1,
+ Archival: false,
+ GossipFanout: 25,
+ NetAddress: "Denver",
+ ReconnectTime: 60 * time.Nanosecond,
+ },
+ out: `{
+ "Archival": false,
+ "GossipFanout": 25,
+ "ReconnectTime": 60
+}`,
+ }, {
+ name: "ignore",
+ in: defaultObject,
+ ignore: []string{"Version", "Archival", "GossipFanout", "NetAddress", "ReconnectTime"},
+ out: `{
+ "Version": 1,
+ "Archival": true,
+ "GossipFanout": 50,
+ "NetAddress": "Denver",
+ "ReconnectTime": 60000000000
+}`,
+ },
+ }
+
+ for _, tc := range testcases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+ a := require.New(t)
+ var writer bytes.Buffer
+ err := WriteNonDefaultValues(&writer, tc.in, defaultObject, tc.ignore)
+ a.NoError(err)
+ a.Equal(tc.out, writer.String())
+ })
+ }
+}
diff --git a/util/condvar/timedwait.go b/util/condvar/timedwait.go
index e14f2b33b7..7b275bfb3a 100644
--- a/util/condvar/timedwait.go
+++ b/util/condvar/timedwait.go
@@ -32,12 +32,12 @@ import (
// This function does not indicate whether a timeout occurred or not;
// the caller should check time.Now() as needed.
func TimedWait(c *sync.Cond, timeout time.Duration) {
- var done int32
+ var done atomic.Bool
go func() {
util.NanoSleep(timeout)
- for atomic.LoadInt32(&done) == 0 {
+ for !done.Load() {
c.Broadcast()
// It is unlikely but possible that the parent
@@ -49,5 +49,5 @@ func TimedWait(c *sync.Cond, timeout time.Duration) {
}()
c.Wait()
- atomic.StoreInt32(&done, 1)
+ done.Store(true)
}
diff --git a/ledger/persistedkvs.go b/util/list.go
similarity index 59%
rename from ledger/persistedkvs.go
rename to util/list.go
index b97234f343..be52459f7c 100644
--- a/ledger/persistedkvs.go
+++ b/util/list.go
@@ -14,47 +14,50 @@
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see .
-package ledger
+package util
-// persistedKVDataList represents a doubly linked list.
-// must initiate with newPersistedKVList.
-type persistedKVDataList struct {
- root persistedKVDataListNode // sentinel list element, only &root, root.prev, and root.next are used
- freeList *persistedKVDataListNode // preallocated nodes location
+// List represents a doubly linked list.
+// must initiate with NewList.
+type List[T any] struct {
+ root ListNode[T] // sentinel list element, only &root, root.prev, and root.next are used
+ freeList *ListNode[T] // preallocated nodes location
}
-type persistedKVDataListNode struct {
+// ListNode represent a list node holding next/prev pointers and a value of type T.
+type ListNode[T any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
- next, prev *persistedKVDataListNode
+ next, prev *ListNode[T]
- Value *cachedKVData
+ Value T
}
-func newPersistedKVList() *persistedKVDataList {
- l := new(persistedKVDataList)
+// NewList creates a new list for storing values of type T.
+func NewList[T any]() *List[T] {
+ l := new(List[T])
l.root.next = &l.root
l.root.prev = &l.root
// used as a helper but does not store value
- l.freeList = new(persistedKVDataListNode)
+ l.freeList = new(ListNode[T])
return l
}
-func (l *persistedKVDataList) insertNodeToFreeList(otherNode *persistedKVDataListNode) {
+func (l *List[T]) insertNodeToFreeList(otherNode *ListNode[T]) {
otherNode.next = l.freeList.next
otherNode.prev = nil
- otherNode.Value = nil
+ var empty T
+ otherNode.Value = empty
l.freeList.next = otherNode
}
-func (l *persistedKVDataList) getNewNode() *persistedKVDataListNode {
+func (l *List[T]) getNewNode() *ListNode[T] {
if l.freeList.next == nil {
- return new(persistedKVDataListNode)
+ return new(ListNode[T])
}
newNode := l.freeList.next
l.freeList.next = newNode.next
@@ -62,20 +65,21 @@ func (l *persistedKVDataList) getNewNode() *persistedKVDataListNode {
return newNode
}
-func (l *persistedKVDataList) allocateFreeNodes(numAllocs int) *persistedKVDataList {
+// AllocateFreeNodes adds N nodes to the free list
+func (l *List[T]) AllocateFreeNodes(numAllocs int) *List[T] {
if l.freeList == nil {
return l
}
for i := 0; i < numAllocs; i++ {
- l.insertNodeToFreeList(new(persistedKVDataListNode))
+ l.insertNodeToFreeList(new(ListNode[T]))
}
return l
}
// Back returns the last element of list l or nil if the list is empty.
-func (l *persistedKVDataList) back() *persistedKVDataListNode {
- isEmpty := func(list *persistedKVDataList) bool {
+func (l *List[T]) Back() *ListNode[T] {
+ isEmpty := func(list *List[T]) bool {
// assumes we are inserting correctly to the list - using pushFront.
return list.root.next == &list.root
}
@@ -85,10 +89,9 @@ func (l *persistedKVDataList) back() *persistedKVDataListNode {
return l.root.prev
}
-// remove removes e from l if e is an element of list l.
-// It returns the element value e.Value.
+// Remove removes e from l if e is an element of list l.
// The element must not be nil.
-func (l *persistedKVDataList) remove(e *persistedKVDataListNode) {
+func (l *List[T]) Remove(e *ListNode[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
@@ -97,15 +100,15 @@ func (l *persistedKVDataList) remove(e *persistedKVDataListNode) {
l.insertNodeToFreeList(e)
}
-// pushFront inserts a new element e with value v at the front of list l and returns e.
-func (l *persistedKVDataList) pushFront(v *cachedKVData) *persistedKVDataListNode {
+// PushFront inserts a new element e with value v at the front of list l and returns e.
+func (l *List[T]) PushFront(v T) *ListNode[T] {
newNode := l.getNewNode()
newNode.Value = v
return l.insertValue(newNode, &l.root)
}
// insertValue inserts e after at, increments l.len, and returns e.
-func (l *persistedKVDataList) insertValue(newNode *persistedKVDataListNode, at *persistedKVDataListNode) *persistedKVDataListNode {
+func (l *List[T]) insertValue(newNode *ListNode[T], at *ListNode[T]) *ListNode[T] {
n := at.next
at.next = newNode
newNode.prev = at
@@ -115,10 +118,10 @@ func (l *persistedKVDataList) insertValue(newNode *persistedKVDataListNode, at *
return newNode
}
-// moveToFront moves element e to the front of list l.
+// MoveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
-func (l *persistedKVDataList) moveToFront(e *persistedKVDataListNode) {
+func (l *List[T]) MoveToFront(e *ListNode[T]) {
if l.root.next == e {
return
}
@@ -126,7 +129,7 @@ func (l *persistedKVDataList) moveToFront(e *persistedKVDataListNode) {
}
// move moves e to next to at and returns e.
-func (l *persistedKVDataList) move(e, at *persistedKVDataListNode) *persistedKVDataListNode {
+func (l *List[T]) move(e, at *ListNode[T]) *ListNode[T] {
if e == at {
return e
}
diff --git a/util/list_test.go b/util/list_test.go
new file mode 100644
index 0000000000..4b87bef745
--- /dev/null
+++ b/util/list_test.go
@@ -0,0 +1,276 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package util
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+)
+
+func checkLen[T any](list *List[T]) int {
+ if list.root.next == &list.root {
+ return 0
+ }
+
+ return countListSize(&list.root)
+}
+
+func countListSize[T any](head *ListNode[T]) (counter int) {
+ for i := head.next; i != head && i != nil; i = i.next {
+ counter++
+ }
+ return counter
+}
+
+func checkListLen[T any](t *testing.T, l *List[T], len int) bool {
+ if n := checkLen(l); n != len {
+ t.Errorf("l.Len() = %d, want %d", n, len)
+ return true
+ }
+ return false
+}
+
+func checkListPointers[T any](t *testing.T, l *List[T], es []*ListNode[T]) {
+ root := &l.root
+
+ if failed := checkListLen(t, l, len(es)); failed {
+ return
+ }
+
+ if failed := zeroListInspection(t, l, len(es), root); failed {
+ return
+ }
+
+ pointerInspection(t, es, root)
+}
+
+func zeroListInspection[T any](t *testing.T, l *List[T], len int, root *ListNode[T]) bool {
+ // zero length lists must be the zero value or properly initialized (sentinel circle)
+ if len == 0 {
+ if l.root.next != nil && l.root.next != root || l.root.prev != nil && l.root.prev != root {
+ t.Errorf("l.root.next = %p, l.root.prev = %p; both should both be nil or %p", l.root.next, l.root.prev, root)
+ }
+ return true
+ }
+ return false
+}
+
+func pointerInspection[T any](t *testing.T, es []*ListNode[T], root *ListNode[T]) {
+ // check internal and external prev/next connections
+ for i, e := range es {
+ prev := root
+ if i > 0 {
+ prev = es[i-1]
+ }
+ if p := e.prev; p != prev {
+ t.Errorf("elt[%d](%p).prev = %p, want %p", i, e, p, prev)
+ }
+
+ next := root
+ if i < len(es)-1 {
+ next = es[i+1]
+ }
+ if n := e.next; n != next {
+ t.Errorf("elt[%d](%p).next = %p, want %p", i, e, n, next)
+ }
+ }
+}
+
+type testVal struct {
+ val int
+}
+
+func TestList_RemoveFromList(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ e1 := l.PushFront(&testVal{1})
+ e2 := l.PushFront(&testVal{2})
+ e3 := l.PushFront(&testVal{3})
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e2, e1})
+
+ l.Remove(e2)
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e1})
+ l.Remove(e3)
+ checkListPointers(t, l, []*ListNode[*testVal]{e1})
+}
+
+func TestList_AddingNewNodeWithAllocatedFreeListPtr(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]().AllocateFreeNodes(10)
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+ if countListSize(l.freeList) != 10 {
+ t.Errorf("free list did not allocate nodes")
+ return
+ }
+ // test elements
+ e1 := l.PushFront(&testVal{1})
+ checkListPointers(t, l, []*ListNode[*testVal]{e1})
+
+ if countListSize(l.freeList) != 9 {
+ t.Errorf("free list did not provide a node on new list entry")
+ return
+ }
+}
+
+func TestList_AddingNewNodeWithAllocatedFreeListValue(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[testVal]().AllocateFreeNodes(10)
+ checkListPointers(t, l, []*ListNode[testVal]{})
+ if countListSize(l.freeList) != 10 {
+ t.Errorf("free list did not allocate nodes")
+ return
+ }
+ // test elements
+ e1 := l.PushFront(testVal{1})
+ checkListPointers(t, l, []*ListNode[testVal]{e1})
+
+ if countListSize(l.freeList) != 9 {
+ t.Errorf("free list did not provide a node on new list entry")
+ return
+ }
+}
+
+func TestList_MultiElementListPositioning(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+ // test elements
+ e2 := l.PushFront(&testVal{2})
+ e1 := l.PushFront(&testVal{1})
+ e3 := l.PushFront(&testVal{3})
+ e4 := l.PushFront(&testVal{4})
+ e5 := l.PushFront(&testVal{5})
+ checkListPointers(t, l, []*ListNode[*testVal]{e5, e4, e3, e1, e2})
+
+ l.move(e4, e1)
+ checkListPointers(t, l, []*ListNode[*testVal]{e5, e3, e1, e4, e2})
+
+ l.Remove(e5)
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e1, e4, e2})
+
+ l.move(e1, e4) // swap in middle
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e4, e1, e2})
+
+ l.MoveToFront(e4)
+ checkListPointers(t, l, []*ListNode[*testVal]{e4, e3, e1, e2})
+
+ l.Remove(e2)
+ checkListPointers(t, l, []*ListNode[*testVal]{e4, e3, e1})
+
+ l.MoveToFront(e3) // move from middle
+ checkListPointers(t, l, []*ListNode[*testVal]{e3, e4, e1})
+
+ l.MoveToFront(e1) // move from end
+ checkListPointers(t, l, []*ListNode[*testVal]{e1, e3, e4})
+
+ l.MoveToFront(e1) // no movement
+ checkListPointers(t, l, []*ListNode[*testVal]{e1, e3, e4})
+
+ e2 = l.PushFront(&testVal{2})
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1, e3, e4})
+
+ l.Remove(e3) // removing from middle
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1, e4})
+
+ l.Remove(e4) // removing from end
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1})
+
+ l.move(e2, e1) // swapping between two elements
+ checkListPointers(t, l, []*ListNode[*testVal]{e1, e2})
+
+ l.Remove(e1) // removing front
+ checkListPointers(t, l, []*ListNode[*testVal]{e2})
+
+ l.move(e2, l.Back()) // swapping element with itself.
+ checkListPointers(t, l, []*ListNode[*testVal]{e2})
+
+ l.Remove(e2) // remove last one
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+}
+
+func TestList_SingleElementListPositioning(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+ e := l.PushFront(&testVal{1})
+ checkListPointers(t, l, []*ListNode[*testVal]{e})
+ l.MoveToFront(e)
+ checkListPointers(t, l, []*ListNode[*testVal]{e})
+ l.Remove(e)
+ checkListPointers(t, l, []*ListNode[*testVal]{})
+}
+
+func TestList_RemovedNodeShouldBeMovedToFreeList(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[*testVal]()
+ e1 := l.PushFront(&testVal{1})
+ e2 := l.PushFront(&testVal{2})
+
+ checkListPointers(t, l, []*ListNode[*testVal]{e2, e1})
+
+ e := l.Back()
+ l.Remove(e)
+
+ for i := l.freeList.next; i != nil; i = i.next {
+ if i == e {
+ // stopping the test with good results:
+ return
+ }
+ }
+ t.Error("expected the removed node to appear at the freelist")
+}
+
+func TestList_PushMoveBackRemove(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ l := NewList[testVal]().AllocateFreeNodes(4)
+ e1 := l.PushFront(testVal{1})
+ e2 := l.PushFront(testVal{2})
+ checkListPointers(t, l, []*ListNode[testVal]{e2, e1})
+
+ l.MoveToFront(e1)
+ checkListPointers(t, l, []*ListNode[testVal]{e1, e2})
+
+ e := l.Back()
+ require.Equal(t, e, e2)
+ l.Remove(e)
+ checkListPointers(t, l, []*ListNode[testVal]{e1})
+
+ e = l.Back()
+ require.Equal(t, e, e1)
+ l.Remove(e)
+ checkListPointers(t, l, []*ListNode[testVal]{})
+
+ e = l.Back()
+ require.Nil(t, e)
+}
diff --git a/util/metrics/counter.go b/util/metrics/counter.go
index 2efb52be52..db0c6e6863 100644
--- a/util/metrics/counter.go
+++ b/util/metrics/counter.go
@@ -20,7 +20,6 @@ import (
"math"
"strconv"
"strings"
- "sync/atomic"
"time"
)
@@ -111,7 +110,7 @@ func (counter *Counter) AddMicrosecondsSince(t time.Time, labels map[string]stri
// GetUint64Value returns the value of the counter.
func (counter *Counter) GetUint64Value() (x uint64) {
- return atomic.LoadUint64(&counter.intValue)
+ return counter.intValue.Load()
}
// GetUint64ValueForLabels returns the value of the counter for the given labels or 0 if it's not found.
@@ -128,7 +127,7 @@ func (counter *Counter) GetUint64ValueForLabels(labels map[string]string) uint64
}
func (counter *Counter) fastAddUint64(x uint64) {
- if atomic.AddUint64(&counter.intValue, x) == x {
+ if counter.intValue.Add(x) == x {
// What we just added is the whole value, this
// is the first Add. Create a dummy
// counterValue for the no-labels value.
@@ -202,7 +201,7 @@ func (counter *Counter) WriteMetric(buf *strings.Builder, parentLabels string) {
buf.WriteString("} ")
value := l.counter
if len(l.labels) == 0 {
- value += atomic.LoadUint64(&counter.intValue)
+ value += counter.intValue.Load()
}
buf.WriteString(strconv.FormatUint(value, 10))
buf.WriteString("\n")
@@ -221,7 +220,7 @@ func (counter *Counter) AddMetric(values map[string]float64) {
for _, l := range counter.values {
sum := l.counter
if len(l.labels) == 0 {
- sum += atomic.LoadUint64(&counter.intValue)
+ sum += counter.intValue.Load()
}
var suffix string
if len(l.formattedLabels) > 0 {
diff --git a/util/metrics/counterCommon.go b/util/metrics/counterCommon.go
index 2a810ace69..dc187b3b48 100644
--- a/util/metrics/counterCommon.go
+++ b/util/metrics/counterCommon.go
@@ -17,14 +17,15 @@
package metrics
import (
+ "sync/atomic"
+
"github.com/algorand/go-deadlock"
)
// Counter represent a single counter variable.
type Counter struct {
// Collects value for special fast-path with no labels through Inc(nil) AddUint64(x, nil)
- // We want to make it on a 64-bit aligned address for ARM compiliers as it's being used by AddUint64
- intValue uint64
+ intValue atomic.Uint64
deadlock.Mutex
name string
diff --git a/util/metrics/gauge.go b/util/metrics/gauge.go
index ce203d47c0..593be0e9d5 100644
--- a/util/metrics/gauge.go
+++ b/util/metrics/gauge.go
@@ -24,7 +24,7 @@ import (
// Gauge represent a single gauge variable.
type Gauge struct {
- value uint64
+ value atomic.Uint64
name string
description string
}
@@ -59,12 +59,12 @@ func (gauge *Gauge) Deregister(reg *Registry) {
// Add increases gauge by x
func (gauge *Gauge) Add(x uint64) {
- atomic.AddUint64(&gauge.value, x)
+ gauge.value.Add(x)
}
// Set sets gauge to x
func (gauge *Gauge) Set(x uint64) {
- atomic.StoreUint64(&gauge.value, x)
+ gauge.value.Store(x)
}
// WriteMetric writes the metric into the output stream
@@ -82,14 +82,14 @@ func (gauge *Gauge) WriteMetric(buf *strings.Builder, parentLabels string) {
buf.WriteString(parentLabels)
}
buf.WriteString("} ")
- value := atomic.LoadUint64(&gauge.value)
+ value := gauge.value.Load()
buf.WriteString(strconv.FormatUint(value, 10))
buf.WriteString("\n")
}
// AddMetric adds the metric into the map
func (gauge *Gauge) AddMetric(values map[string]float64) {
- value := atomic.LoadUint64(&gauge.value)
+ value := gauge.value.Load()
values[sanitizeTelemetryName(gauge.name)] = float64(value)
}
diff --git a/util/metrics/metrics.go b/util/metrics/metrics.go
index cb376eb0a3..cebece25fc 100644
--- a/util/metrics/metrics.go
+++ b/util/metrics/metrics.go
@@ -123,6 +123,8 @@ var (
TransactionMessagesDupRawMsg = MetricName{Name: "algod_transaction_messages_dropped_dup_raw", Description: "Number of dupe raw transaction messages dropped"}
// TransactionMessagesDupCanonical "Number of transaction messages dropped after canonical re-encoding"
TransactionMessagesDupCanonical = MetricName{Name: "algod_transaction_messages_dropped_dup_canonical", Description: "Number of transaction messages dropped after canonical re-encoding"}
+ // TransactionMessagesAppLimiterDrop "Number of transaction messages dropped after app limits check"
+ TransactionMessagesAppLimiterDrop = MetricName{Name: "algod_transaction_messages_dropped_app_limiter", Description: "Number of transaction messages dropped after app limits check"}
// TransactionMessagesBacklogSize "Number of transaction messages in the TX handler backlog queue"
TransactionMessagesBacklogSize = MetricName{Name: "algod_transaction_messages_backlog_size", Description: "Number of transaction messages in the TX handler backlog queue"}
diff --git a/util/set.go b/util/set.go
new file mode 100644
index 0000000000..a23f543dd6
--- /dev/null
+++ b/util/set.go
@@ -0,0 +1,42 @@
+// Copyright (C) 2019-2023 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package util
+
+// Set is a type alias for map with empty struct{}, where keys are comparable
+// We don't attempt to move even forward for the generics,
+// for keys being comparable should be sufficient for most cases.
+// (Though we actually want compare byte slices, but seems not achievable at this moment)
+type Set[T comparable] map[T]struct{}
+
+// Add adds variate number of elements to the set.
+func (s Set[T]) Add(elems ...T) Set[T] {
+ for _, elem := range elems {
+ s[elem] = struct{}{}
+ }
+ return s
+}
+
+// MakeSet constructs a set instance directly from elements.
+func MakeSet[T comparable](elems ...T) Set[T] {
+ return make(Set[T]).Add(elems...)
+}
+
+// Contains checks the membership of an element in the set.
+func (s Set[T]) Contains(elem T) (exists bool) {
+ _, exists = s[elem]
+ return
+}
diff --git a/util/timers/frozen.go b/util/timers/frozen.go
index e6487b1a8a..f4400c3a82 100644
--- a/util/timers/frozen.go
+++ b/util/timers/frozen.go
@@ -21,42 +21,42 @@ import (
)
// Frozen is a dummy frozen clock that never fires.
-type Frozen struct {
+type Frozen[TimeoutType comparable] struct {
timeoutCh chan time.Time
}
// MakeFrozenClock creates a new frozen clock.
-func MakeFrozenClock() Clock {
- return &Frozen{
+func MakeFrozenClock[TimeoutType comparable]() Clock[TimeoutType] {
+ return &Frozen[TimeoutType]{
timeoutCh: make(chan time.Time, 1),
}
}
// Zero returns a new Clock reset to the current time.
-func (m *Frozen) Zero() Clock {
- return MakeFrozenClock()
+func (m *Frozen[TimeoutType]) Zero() Clock[TimeoutType] {
+ return MakeFrozenClock[TimeoutType]()
}
// TimeoutAt returns a channel that will signal when the duration has elapsed.
-func (m *Frozen) TimeoutAt(delta time.Duration) <-chan time.Time {
+func (m *Frozen[TimeoutType]) TimeoutAt(delta time.Duration, timeoutType TimeoutType) <-chan time.Time {
return m.timeoutCh
}
// Encode implements Clock.Encode.
-func (m *Frozen) Encode() []byte {
+func (m *Frozen[TimeoutType]) Encode() []byte {
return []byte{}
}
// Decode implements Clock.Decode.
-func (m *Frozen) Decode([]byte) (Clock, error) {
- return MakeFrozenClock(), nil
+func (m *Frozen[TimeoutType]) Decode([]byte) (Clock[TimeoutType], error) {
+ return MakeFrozenClock[TimeoutType](), nil
}
-func (m *Frozen) String() string {
+func (m *Frozen[TimeoutType]) String() string {
return ""
}
// Since implements the Clock interface.
-func (m *Frozen) Since() time.Duration {
+func (m *Frozen[TimeoutType]) Since() time.Duration {
return 0
}
diff --git a/util/timers/interface.go b/util/timers/interface.go
index e96aced757..d217437e9e 100644
--- a/util/timers/interface.go
+++ b/util/timers/interface.go
@@ -22,20 +22,23 @@ import (
)
// Clock provides timeout events which fire at some point after a point in time.
-type Clock interface {
+type Clock[TimeoutType comparable] interface {
// Zero returns a reset Clock. TimeoutAt channels will use the point
// at which Zero was called as their reference point.
- Zero() Clock
+ Zero() Clock[TimeoutType]
// Since returns the time spent between the last time the clock was zeroed out and the current
// wall clock time.
Since() time.Duration
// TimeoutAt returns a channel that fires delta time after Zero was called.
+ // timeoutType is specifies the reason for this timeout. If there are two
+ // timeouts of the same type at the same time, then only one of them fires.
// If delta has already passed, it returns a closed channel.
//
- // TimeoutAt must be called after Zero; otherwise, the channel's behavior is undefined.
- TimeoutAt(delta time.Duration) <-chan time.Time
+ // TimeoutAt must be called after Zero; otherwise, the channel's behavior is
+ // undefined.
+ TimeoutAt(delta time.Duration, timeoutType TimeoutType) <-chan time.Time
// Encode serializes the Clock into a byte slice.
Encode() []byte
@@ -43,5 +46,5 @@ type Clock interface {
// Decode deserializes the Clock from a byte slice.
// A Clock which has been Decoded from an Encoded Clock should produce
// the same timeouts as the original Clock.
- Decode([]byte) (Clock, error)
+ Decode([]byte) (Clock[TimeoutType], error)
}
diff --git a/util/timers/monotonic.go b/util/timers/monotonic.go
index 70db87da3e..96207f3950 100644
--- a/util/timers/monotonic.go
+++ b/util/timers/monotonic.go
@@ -23,56 +23,66 @@ import (
"github.com/algorand/go-algorand/protocol"
)
+type timeout struct {
+ delta time.Duration
+ ch <-chan time.Time
+}
+
// Monotonic uses the system's monotonic clock to emit timeouts.
-type Monotonic struct {
+type Monotonic[TimeoutType comparable] struct {
zero time.Time
- timeouts map[time.Duration]<-chan time.Time
+ timeouts map[TimeoutType]timeout
}
// MakeMonotonicClock creates a new monotonic clock with a given zero point.
-func MakeMonotonicClock(zero time.Time) Clock {
- return &Monotonic{
+func MakeMonotonicClock[TimeoutType comparable](zero time.Time) Clock[TimeoutType] {
+ return &Monotonic[TimeoutType]{
zero: zero,
}
}
// Zero returns a new Clock reset to the current time.
-func (m *Monotonic) Zero() Clock {
+func (m *Monotonic[TimeoutType]) Zero() Clock[TimeoutType] {
z := time.Now()
logging.Base().Debugf("Clock zeroed to %v", z)
- return MakeMonotonicClock(z)
+ return MakeMonotonicClock[TimeoutType](z)
}
// TimeoutAt returns a channel that will signal when the duration has elapsed.
-func (m *Monotonic) TimeoutAt(delta time.Duration) <-chan time.Time {
+func (m *Monotonic[TimeoutType]) TimeoutAt(delta time.Duration, timeoutType TimeoutType) <-chan time.Time {
if m.timeouts == nil {
- m.timeouts = make(map[time.Duration]<-chan time.Time)
+ m.timeouts = make(map[TimeoutType]timeout)
}
- timeoutCh, ok := m.timeouts[delta]
- if ok {
- return timeoutCh
+
+ tmt, ok := m.timeouts[timeoutType]
+ if ok && tmt.delta == delta {
+ // if the new timeout is the same as the current one for that type,
+ // return the existing channel.
+ return tmt.ch
}
+ tmt = timeout{delta: delta}
+
target := m.zero.Add(delta)
left := target.Sub(time.Now())
if left < 0 {
- timeout := make(chan time.Time)
- close(timeout)
- timeoutCh = timeout
+ ch := make(chan time.Time)
+ close(ch)
+ tmt.ch = ch
} else {
- timeoutCh = time.After(left)
+ tmt.ch = time.After(left)
}
- m.timeouts[delta] = timeoutCh
- return timeoutCh
+ m.timeouts[timeoutType] = tmt
+ return tmt.ch
}
// Encode implements Clock.Encode.
-func (m *Monotonic) Encode() []byte {
+func (m *Monotonic[TimeoutType]) Encode() []byte {
return protocol.EncodeReflect(m.zero)
}
// Decode implements Clock.Decode.
-func (m *Monotonic) Decode(data []byte) (Clock, error) {
+func (m *Monotonic[TimeoutType]) Decode(data []byte) (Clock[TimeoutType], error) {
var zero time.Time
err := protocol.DecodeReflect(data, &zero)
if err == nil {
@@ -80,14 +90,14 @@ func (m *Monotonic) Decode(data []byte) (Clock, error) {
} else {
logging.Base().Errorf("Clock decoded with zero at %v (err: %v)", zero, err)
}
- return MakeMonotonicClock(zero), err
+ return MakeMonotonicClock[TimeoutType](zero), err
}
-func (m *Monotonic) String() string {
+func (m *Monotonic[TimeoutType]) String() string {
return time.Time(m.zero).String()
}
// Since returns the time that has passed between the time the clock was last zeroed out and now
-func (m *Monotonic) Since() time.Duration {
+func (m *Monotonic[TimeoutType]) Since() time.Duration {
return time.Since(m.zero)
}
diff --git a/util/timers/monotonic_test.go b/util/timers/monotonic_test.go
index f8821b300b..912f1b8425 100644
--- a/util/timers/monotonic_test.go
+++ b/util/timers/monotonic_test.go
@@ -17,10 +17,11 @@
package timers
import (
- "github.com/algorand/go-algorand/test/partitiontest"
"math/rand"
"testing"
"time"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
)
func polled(ch <-chan time.Time) bool {
@@ -35,14 +36,14 @@ func polled(ch <-chan time.Time) bool {
func TestMonotonicDelta(t *testing.T) {
partitiontest.PartitionTest(t)
- var m Monotonic
- var c Clock
+ var m Monotonic[int]
+ var c Clock[int]
var ch <-chan time.Time
d := time.Millisecond * 100
c = m.Zero()
- ch = c.TimeoutAt(d)
+ ch = c.TimeoutAt(d, 0)
if polled(ch) {
t.Errorf("channel fired ~100ms early")
}
@@ -52,7 +53,7 @@ func TestMonotonicDelta(t *testing.T) {
t.Errorf("channel failed to fire at 100ms")
}
- ch = c.TimeoutAt(d / 2)
+ ch = c.TimeoutAt(d/2, 0)
if !polled(ch) {
t.Errorf("channel failed to fire at 50ms")
}
@@ -61,12 +62,12 @@ func TestMonotonicDelta(t *testing.T) {
func TestMonotonicZeroDelta(t *testing.T) {
partitiontest.PartitionTest(t)
- var m Monotonic
- var c Clock
+ var m Monotonic[int]
+ var c Clock[int]
var ch <-chan time.Time
c = m.Zero()
- ch = c.TimeoutAt(0)
+ ch = c.TimeoutAt(0, 0)
if !polled(ch) {
t.Errorf("read failed on channel at zero timeout")
}
@@ -75,12 +76,12 @@ func TestMonotonicZeroDelta(t *testing.T) {
func TestMonotonicNegativeDelta(t *testing.T) {
partitiontest.PartitionTest(t)
- var m Monotonic
- var c Clock
+ var m Monotonic[int]
+ var c Clock[int]
var ch <-chan time.Time
c = m.Zero()
- ch = c.TimeoutAt(-time.Second)
+ ch = c.TimeoutAt(-time.Second, 0)
if !polled(ch) {
t.Errorf("read failed on channel at negative timeout")
}
@@ -89,14 +90,14 @@ func TestMonotonicNegativeDelta(t *testing.T) {
func TestMonotonicZeroTwice(t *testing.T) {
partitiontest.PartitionTest(t)
- var m Monotonic
- var c Clock
+ var m Monotonic[int]
+ var c Clock[int]
var ch <-chan time.Time
d := time.Millisecond * 100
c = m.Zero()
- ch = c.TimeoutAt(d)
+ ch = c.TimeoutAt(d, 0)
if polled(ch) {
t.Errorf("channel fired ~100ms early")
}
@@ -107,7 +108,7 @@ func TestMonotonicZeroTwice(t *testing.T) {
}
c = c.Zero()
- ch = c.TimeoutAt(d)
+ ch = c.TimeoutAt(d, 0)
if polled(ch) {
t.Errorf("channel fired ~100ms early after call to Zero")
}
@@ -121,21 +122,21 @@ func TestMonotonicZeroTwice(t *testing.T) {
func TestMonotonicEncodeDecode(t *testing.T) {
partitiontest.PartitionTest(t)
- singleTest := func(c Clock, descr string) {
+ singleTest := func(c Clock[int], descr string) {
data := c.Encode()
c0, err := c.Decode(data)
if err != nil {
t.Errorf("decoding error: %v", err)
}
- if !time.Time(c.(*Monotonic).zero).Equal(time.Time(c0.(*Monotonic).zero)) {
+ if !time.Time(c.(*Monotonic[int]).zero).Equal(time.Time(c0.(*Monotonic[int]).zero)) {
t.Errorf("%v clock not encoded properly: %v != %v", descr, c, c0)
}
}
- var c Clock
- var m Monotonic
+ var c Clock[int]
+ var m Monotonic[int]
- c = Clock(&m)
+ c = Clock[int](&m)
singleTest(c, "empty")
c = c.Zero()
@@ -144,11 +145,51 @@ func TestMonotonicEncodeDecode(t *testing.T) {
now := time.Now()
for i := 0; i < 100; i++ {
r := time.Duration(rand.Int63())
- c = Clock(
- &Monotonic{
+ c = Clock[int](
+ &Monotonic[int]{
zero: now.Add(r),
},
)
singleTest(c, "random")
}
}
+
+func TestTimeoutTypes(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ var m Monotonic[int]
+ var c Clock[int]
+
+ d := time.Millisecond * 100
+
+ c = m.Zero()
+ ch1 := c.TimeoutAt(d, 0)
+ ch2 := c.TimeoutAt(d, 1)
+ if polled(ch1) {
+ t.Errorf("channel fired ~100ms early")
+ }
+ if polled(ch2) {
+ t.Errorf("channel fired ~100ms early")
+ }
+
+ if ch1 == ch2 {
+ t.Errorf("equal channels for different timeout types")
+ }
+
+ <-time.After(d * 2)
+ if !polled(ch1) {
+ t.Errorf("channel failed to fire at 100ms")
+ }
+ if !polled(ch2) {
+ t.Errorf("channel failed to fire at 100ms")
+ }
+
+ ch1 = c.TimeoutAt(d/2, 0)
+ if !polled(ch1) {
+ t.Errorf("channel failed to fire at 50ms")
+ }
+ ch2 = c.TimeoutAt(d/2, 0)
+ if !polled(ch2) {
+ t.Errorf("channel failed to fire at 50ms")
+ }
+}