From 92af3721ea120d6e56efb8f3433e12b616a4a0d2 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 2 Nov 2023 10:51:39 -0400 Subject: [PATCH 001/218] tests: add refreshAvailablePeers catchup unit test (#5815) --- catchup/peerSelector.go | 10 +-- catchup/peerSelector_test.go | 133 ++++++++++++++++++++++++++++------- 2 files changed, 115 insertions(+), 28 deletions(-) diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index a75e7c61f1..0cdc14cf64 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -110,11 +110,13 @@ type peerPool struct { // client to provide feedback regarding the peer's performance, and to have the subsequent // query(s) take advantage of that intel. type peerSelector struct { - mu deadlock.Mutex - net peersRetriever + mu deadlock.Mutex + net peersRetriever + // peerClasses is the list of peer classes we want to have in the peerSelector. peerClasses []peerClass - pools []peerPool - counter uint64 + // pools is the list of peer pools, each pool contains a list of peers with the same rank. + pools []peerPool + counter uint64 } // historicStats stores the past windowSize ranks for the peer passed diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index 4991143ef9..680b65cf37 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -21,9 +21,11 @@ import ( "context" "encoding/binary" "net/http" + "reflect" "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/crypto" @@ -68,8 +70,9 @@ func (d *mockUnicastPeer) GetConnectionLatency() time.Duration { return time.Duration(0) } -func TestPeerAddress(t *testing.T) { +func TestPeerSelector_PeerAddress(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() httpPeer := &mockHTTPPeer{address: "12345"} require.Equal(t, "12345", peerAddress(httpPeer)) @@ -81,8 +84,9 @@ func TestPeerAddress(t *testing.T) { require.Equal(t, "", peerAddress(t)) } -func TestDownloadDurationToRank(t *testing.T) { +func TestPeerSelector_DownloadDurationToRank(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() // verify mid value require.Equal(t, 1500, downloadDurationToRank(50*time.Millisecond, 0*time.Millisecond, 100*time.Millisecond, 1000, 2000)) @@ -121,8 +125,9 @@ func makePeersRetrieverStub(fnc func(options ...network.PeerOption) []network.Pe getPeersStub: fnc, } } -func TestPeerSelector(t *testing.T) { +func TestPeerSelector_RankPeer(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers := []network.Peer{&mockHTTPPeer{address: "12345"}} @@ -175,14 +180,13 @@ func TestPeerSelector(t *testing.T) { r1, r2 = peerSelector.rankPeer(nil, 10) require.False(t, r1 != r2) - r2, r2 = peerSelector.rankPeer(&peerSelectorPeer{&mockHTTPPeer{address: "abc123"}, 1}, 10) + r1, r2 = peerSelector.rankPeer(&peerSelectorPeer{&mockHTTPPeer{address: "abc123"}, 1}, 10) require.False(t, r1 != r2) - - return } -func TestPeerDownloadRanking(t *testing.T) { +func TestPeerSelector_PeerDownloadRanking(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "1234"}, &mockHTTPPeer{address: "5678"}} peers2 := []network.Peer{&mockHTTPPeer{address: "abcd"}, &mockHTTPPeer{address: "efgh"}} @@ -232,8 +236,9 @@ func TestPeerDownloadRanking(t *testing.T) { require.Equal(t, peerRankInvalidDownload, peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{mockHTTPPeer{address: "abc123"}, 0}, time.Millisecond)) } -func TestFindMissingPeer(t *testing.T) { +func TestPeerSelector_FindMissingPeer(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { @@ -246,8 +251,9 @@ func TestFindMissingPeer(t *testing.T) { require.Equal(t, -1, peerIdx) } -func TestHistoricData(t *testing.T) { +func TestPeerSelector_HistoricData(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} @@ -319,8 +325,9 @@ func peerSelectorTestRandVal(t *testing.T, seed int) float64 { randVal = randVal + 1 return randVal } -func TestPeersDownloadFailed(t *testing.T) { +func TestPeerSelector_PeersDownloadFailed(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} @@ -392,10 +399,11 @@ func TestPeersDownloadFailed(t *testing.T) { } -// TestPenalty tests that the penalty is calculated correctly and one peer +// TestPeerSelector_Penalty tests that the penalty is calculated correctly and one peer // is not dominating all the selection. -func TestPenalty(t *testing.T) { +func TestPeerSelector_Penalty(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} @@ -451,9 +459,10 @@ func TestPenalty(t *testing.T) { require.Equal(t, counters[4], 0) } -// TestPeerDownloadDurationToRank tests all the cases handled by peerDownloadDurationToRank -func TestPeerDownloadDurationToRank(t *testing.T) { +// TestPeerSelector_PeerDownloadDurationToRank tests all the cases handled by peerDownloadDurationToRank +func TestPeerSelector_PeerDownloadDurationToRank(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} @@ -500,8 +509,9 @@ func TestPeerDownloadDurationToRank(t *testing.T) { } -func TestLowerUpperBounds(t *testing.T) { +func TestPeerSelector_LowerUpperBounds(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, @@ -522,8 +532,9 @@ func TestLowerUpperBounds(t *testing.T) { require.Equal(t, peerRank4HighBlockTime, upperBound(classes[4])) } -func TestFullResetRequestPenalty(t *testing.T) { +func TestPeerSelector_FullResetRequestPenalty(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivers} hs := makeHistoricStatus(10, class) @@ -534,10 +545,11 @@ func TestFullResetRequestPenalty(t *testing.T) { require.Equal(t, 0, len(hs.requestGaps)) } -// TesPenaltyBounds makes sure that the penalty does not demote the peer to a lower class, +// TestPeerSelector_PenaltyBounds makes sure that the penalty does not demote the peer to a lower class, // and resetting the penalty of a demoted peer does not promote it back -func TestPenaltyBounds(t *testing.T) { +func TestPeerSelector_PenaltyBounds(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers} hs := makeHistoricStatus(peerHistoryWindowSize, class) @@ -558,11 +570,12 @@ func TestPenaltyBounds(t *testing.T) { require.Equal(t, peerRankDownloadFailed, r3) } -// TestClassUpperBound makes sure the peer rank does not exceed the class upper bound +// TestPeerSelector_ClassUpperBound makes sure the peer rank does not exceed the class upper bound // This was a bug where the resetRequestPenalty was not bounding the returned rank, and was having download failures. // Initializing rankSamples to 0 makes this works, since the dropped value subtracts 0 from rankSum. -func TestClassUpperBound(t *testing.T) { +func TestPeerSelector_ClassUpperBound(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} @@ -592,11 +605,12 @@ func TestClassUpperBound(t *testing.T) { } } -// TestClassLowerBound makes sure the peer rank does not go under the class lower bound +// TestPeerSelector_ClassLowerBound makes sure the peer rank does not go under the class lower bound // This was a bug where the resetRequestPenalty was not bounding the returned rank, and the rankSum was not // initialized to give the average of class.initialRank -func TestClassLowerBound(t *testing.T) { +func TestPeerSelector_ClassLowerBound(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} @@ -623,8 +637,8 @@ func TestClassLowerBound(t *testing.T) { } } -// TestEviction tests that the peer is evicted after several download failures, and it handles same address for different peer classes -func TestEvictionAndUpgrade(t *testing.T) { +// TestPeerSelector_Eviction tests that the peer is evicted after several download failures, and it handles same address for different peer classes +func TestPeerSelector_EvictionAndUpgrade(t *testing.T) { partitiontest.PartitionTest(t) peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}} @@ -656,5 +670,76 @@ func TestEvictionAndUpgrade(t *testing.T) { peerSelector.rankPeer(psp, peerRankDownloadFailed) } psp, err := peerSelector.getNextPeer() + require.NoError(t, err) require.Equal(t, psp.peerClass, network.PeersPhonebookRelays) } + +// TestPeerSelector_RefreshAvailablePeers tests addition/removal of peers from the pool +func TestPeerSelector_RefreshAvailablePeers(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // check new peers added to the pool + p1 := mockHTTPPeer{address: "p1"} + p2 := mockHTTPPeer{address: "p2"} + ps := peerSelector{ + peerClasses: []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + }, + pools: []peerPool{ + { + rank: peerRankInitialFirstPriority, + peers: []peerPoolEntry{ + { + peer: &p1, + class: peerClass{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + }, + }, + }, + }, + } + + ps.net = makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { + return []network.Peer{&p1, &p2} + }) + + ps.refreshAvailablePeers() + + peerComparer := func(x, y peerPoolEntry) bool { + return reflect.DeepEqual(x.peer, y.peer) + } + + require.Equal(t, 2, len(ps.pools)) + require.Equal(t, 2, len(ps.pools[0].peers)) + require.Equal(t, 2, len(ps.pools[1].peers)) + + require.True(t, cmp.Equal( + ps.pools[0].peers, + []peerPoolEntry{{peer: &p1}, {peer: &p2}}, + cmp.Comparer(peerComparer), + )) + require.True(t, cmp.Equal( + ps.pools[1].peers, + []peerPoolEntry{{peer: &p1}, {peer: &p2}}, + cmp.Comparer(peerComparer), + )) + + // ensure removal peers from a pool and pools themselves + // when returning only p1 for the first class and empty for the second + ps.net = makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { + if options[0] == network.PeersConnectedOut { + return []network.Peer{&p1} + } + return []network.Peer{} + }) + + ps.refreshAvailablePeers() + require.Equal(t, 1, len(ps.pools)) + require.Equal(t, 1, len(ps.pools[0].peers)) + require.True(t, cmp.Equal( + ps.pools[0].peers, + []peerPoolEntry{{peer: &p1}}, + cmp.Comparer(peerComparer), + )) +} From af2a7ee2bffaea8b3385b09035ad73107e34440e Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Tue, 7 Nov 2023 00:50:51 +0900 Subject: [PATCH 002/218] typo: fix comment in catchup/universalFetcher (#5811) --- catchup/universalFetcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index c8dd8b9f9f..0b1ab4abb0 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -132,7 +132,7 @@ func (w *wsFetcherClient) getBlockBytes(ctx context.Context, r basics.Round) ([] defer func() { cancelFunc() // note that we don't need to have additional Unlock here since - // we already have a defered Unlock above ( which executes in reversed order ) + // we already have a deferred Unlock above ( which executes in reversed order ) w.mu.Lock() }() From 5a2ef5eabf5a5ce3a0d892e2f555cd450c0968a3 Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:06:26 -0500 Subject: [PATCH 003/218] ledger: support WaitWithCancel for unsuccessful WaitForBlock API calls (#5814) --- daemon/algod/api/server/v2/handlers.go | 9 +- .../server/v2/test/handlers_resources_test.go | 6 +- ledger/bulletin.go | 48 ++++---- ledger/bulletin_test.go | 108 ++++++++++++++++++ ledger/ledger.go | 10 ++ 5 files changed, 157 insertions(+), 24 deletions(-) diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 065337f32d..0282594f4f 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -22,7 +22,6 @@ import ( "encoding/base64" "errors" "fmt" - "golang.org/x/sync/semaphore" "io" "math" "net/http" @@ -31,6 +30,7 @@ import ( "time" "github.com/labstack/echo/v4" + "golang.org/x/sync/semaphore" "github.com/algorand/avm-abi/apps" "github.com/algorand/go-codec/codec" @@ -96,6 +96,7 @@ type LedgerForAPI interface { LatestTotals() (basics.Round, ledgercore.AccountTotals, error) BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) Wait(r basics.Round) chan struct{} + WaitWithCancel(r basics.Round) (chan struct{}, func()) GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) EncodedBlockCert(rnd basics.Round) (blk []byte, cert []byte, err error) Block(rnd basics.Round) (blk bookkeeping.Block, err error) @@ -940,11 +941,15 @@ func (v2 *Handlers) WaitForBlock(ctx echo.Context, round uint64) error { } // Wait + ledgerWaitCh, cancelLedgerWait := ledger.WaitWithCancel(basics.Round(round + 1)) + defer cancelLedgerWait() select { case <-v2.Shutdown: return internalError(ctx, err, errServiceShuttingDown, v2.Log) + case <-ctx.Request().Context().Done(): + return ctx.NoContent(http.StatusRequestTimeout) case <-time.After(WaitForBlockTimeout): - case <-ledger.Wait(basics.Round(round + 1)): + case <-ledgerWaitCh: } // Return status after the wait diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index 1de86ddc19..adf187053a 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -19,11 +19,12 @@ package test import ( "encoding/json" "fmt" - "github.com/algorand/go-algorand/data/transactions/logic" "net/http" "net/http/httptest" "testing" + "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -135,6 +136,9 @@ func (l *mockLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) func (l *mockLedger) Wait(r basics.Round) chan struct{} { panic("not implemented") } +func (l *mockLedger) WaitWithCancel(r basics.Round) (chan struct{}, func()) { + panic("not implemented") +} func (l *mockLedger) GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (c basics.Address, ok bool, err error) { panic("not implemented") } diff --git a/ledger/bulletin.go b/ledger/bulletin.go index 039fb3376e..5dc2b99aaa 100644 --- a/ledger/bulletin.go +++ b/ledger/bulletin.go @@ -18,7 +18,6 @@ package ledger import ( "context" - "sync/atomic" "github.com/algorand/go-deadlock" @@ -28,29 +27,17 @@ import ( "github.com/algorand/go-algorand/ledger/store/trackerdb" ) -// notifier is a struct that encapsulates a single-shot channel; it will only be signaled once. +// notifier is a struct that encapsulates a single-shot channel; it should only be signaled once. type notifier struct { - signal chan struct{} - notified *atomic.Uint32 -} - -// makeNotifier constructs a notifier that has not been signaled. -func makeNotifier() notifier { - return notifier{signal: make(chan struct{}), notified: &atomic.Uint32{}} -} - -// notify signals the channel if it hasn't already done so -func (notifier *notifier) notify() { - if notifier.notified.CompareAndSwap(0, 1) { - close(notifier.signal) - } + signal chan struct{} + count int } // bulletin provides an easy way to wait on a round to be written to the ledger. // To use it, call <-Wait(round). type bulletin struct { mu deadlock.Mutex - pendingNotificationRequests map[basics.Round]notifier + pendingNotificationRequests map[basics.Round]*notifier latestRound basics.Round } @@ -62,7 +49,7 @@ type bulletinMem struct { func makeBulletin() *bulletin { b := new(bulletin) - b.pendingNotificationRequests = make(map[basics.Round]notifier) + b.pendingNotificationRequests = make(map[basics.Round]*notifier) return b } @@ -80,14 +67,32 @@ func (b *bulletin) Wait(round basics.Round) chan struct{} { signal, exists := b.pendingNotificationRequests[round] if !exists { - signal = makeNotifier() + signal = ¬ifier{signal: make(chan struct{})} b.pendingNotificationRequests[round] = signal } + // Increment count of waiters, to support canceling. + signal.count++ + return signal.signal } +// CancelWait removes a wait for a particular round. If no one else is waiting, the +// notifier channel for that round is removed. +func (b *bulletin) CancelWait(round basics.Round) { + b.mu.Lock() + defer b.mu.Unlock() + + signal, exists := b.pendingNotificationRequests[round] + if exists { + signal.count-- + if signal.count <= 0 { + delete(b.pendingNotificationRequests, round) + } + } +} + func (b *bulletin) loadFromDisk(l ledgerForTracker, _ basics.Round) error { - b.pendingNotificationRequests = make(map[basics.Round]notifier) + b.pendingNotificationRequests = make(map[basics.Round]*notifier) b.latestRound = l.Latest() return nil } @@ -105,7 +110,8 @@ func (b *bulletin) notifyRound(rnd basics.Round) { } delete(b.pendingNotificationRequests, pending) - signal.notify() + // signal the channel by closing it; this is under lock and will only happen once + close(signal.signal) } b.latestRound = rnd diff --git a/ledger/bulletin_test.go b/ledger/bulletin_test.go index 5a6f6bb833..88d3784470 100644 --- a/ledger/bulletin_test.go +++ b/ledger/bulletin_test.go @@ -20,7 +20,9 @@ import ( "testing" "time" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" ) const epsilon = 5 * time.Millisecond @@ -100,3 +102,109 @@ func TestBulletin(t *testing.T) { t.Errorf("<-Wait(10) finished late") } } + +func TestCancelWait(t *testing.T) { + partitiontest.PartitionTest(t) + + bul := makeBulletin() + + // Calling Wait before CancelWait + waitCh := bul.Wait(5) + bul.CancelWait(5) + bul.committedUpTo(5) + select { + case <-waitCh: + t.Errorf("<-Wait(5) should have been cancelled") + case <-time.After(epsilon): + // Correct + } + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(5)) + + // Calling CancelWait before Wait + bul.CancelWait(6) + select { + case <-bul.Wait(6): + t.Errorf("<-Wait(6) should have been cancelled") + case <-time.After(epsilon): + // Correct + } + require.Contains(t, bul.pendingNotificationRequests, basics.Round(6)) + require.Equal(t, bul.pendingNotificationRequests[basics.Round(6)].count, 1) + bul.CancelWait(6) + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(6)) + + // Two Waits, one cancelled + waitCh1 := bul.Wait(7) + waitCh2 := bul.Wait(7) + require.Equal(t, waitCh1, waitCh2) + bul.CancelWait(7) + select { + case <-waitCh1: + t.Errorf("<-Wait(7) should not be notified yet") + case <-time.After(epsilon): + // Correct + } + // Still one waiter + require.Contains(t, bul.pendingNotificationRequests, basics.Round(7)) + require.Equal(t, bul.pendingNotificationRequests[basics.Round(7)].count, 1) + + bul.committedUpTo(7) + select { + case <-waitCh1: + // Correct + case <-time.After(epsilon): + t.Errorf("<-Wait(7) should have been notified") + } + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(7)) + + // Wait followed by Cancel for a round that already completed + waitCh = bul.Wait(5) + bul.CancelWait(5) + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(5)) + select { + case <-waitCh: + // Correct + case <-time.After(epsilon): + t.Errorf("<-Wait(5) should have been notified right away") + } + + // Cancel Wait after Wait triggered + waitCh = bul.Wait(8) + require.Contains(t, bul.pendingNotificationRequests, basics.Round(8)) + require.Equal(t, bul.pendingNotificationRequests[basics.Round(8)].count, 1) + bul.committedUpTo(8) + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(8)) + select { + case <-waitCh: + // Correct + case <-time.After(epsilon): + t.Errorf("<-Wait(8) should have been notified") + } + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(8)) + bul.CancelWait(8) // should do nothing + + // Cancel Wait after Wait triggered, but before Wait returned + waitCh = bul.Wait(9) + require.Contains(t, bul.pendingNotificationRequests, basics.Round(9)) + require.Equal(t, bul.pendingNotificationRequests[basics.Round(9)].count, 1) + bul.committedUpTo(9) + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(9)) + bul.CancelWait(9) // should do nothing + select { + case <-waitCh: + // Correct + case <-time.After(epsilon): + t.Errorf("<-Wait(9) should have been notified") + } + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(9)) + + // Two waits, both cancelled + waitCh1 = bul.Wait(10) + waitCh2 = bul.Wait(10) + require.Equal(t, waitCh1, waitCh2) + bul.CancelWait(10) + require.Contains(t, bul.pendingNotificationRequests, basics.Round(10)) + require.Equal(t, bul.pendingNotificationRequests[basics.Round(10)].count, 1) + bul.CancelWait(10) + require.NotContains(t, bul.pendingNotificationRequests, basics.Round(10)) +} diff --git a/ledger/ledger.go b/ledger/ledger.go index 1617007aef..458ce3d532 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -769,6 +769,16 @@ func (l *Ledger) Wait(r basics.Round) chan struct{} { return l.bulletinDisk.Wait(r) } +// WaitWithCancel returns a channel that closes once a given round is +// stored durably in the ledger. The returned function can be used to +// cancel the wait, which cleans up resources if no other Wait call is +// active for the same round. +func (l *Ledger) WaitWithCancel(r basics.Round) (chan struct{}, func()) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + return l.bulletinDisk.Wait(r), func() { l.bulletinDisk.CancelWait(r) } +} + // WaitMem returns a channel that closes once a given round is // available in memory in the ledger, but might not be stored // durably on disk yet. From c1207a41af94d94afd8363047ded9bbabd8d34ad Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:35:47 -0500 Subject: [PATCH 004/218] ledger: make catchpoint generation backward compatible (#5598) --- ledger/catchpointtracker.go | 101 +++++++++++++++++++++---------- ledger/catchpointtracker_test.go | 57 ++++++++++++++++- ledger/tracker.go | 3 + 3 files changed, 128 insertions(+), 33 deletions(-) diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index dd576fc41f..32216fcb20 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -105,9 +105,6 @@ type catchpointTracker struct { // enableGeneratingCatchpointFiles determines whether catchpoints files should be generated by the trackers. enableGeneratingCatchpointFiles bool - // Prepared SQL statements for fast accounts DB lookups. - accountsq trackerdb.AccountsReader - // log copied from ledger log logging.Logger @@ -138,6 +135,9 @@ type catchpointTracker struct { // roundDigest stores the digest of the block for every round starting with dbRound+1 and every round after it. roundDigest []crypto.Digest + // consensusVersion stores the consensus versions for every round starting with dbRound+1 and every round after it. + consensusVersion []protocol.ConsensusVersion + // reenableCatchpointsRound is a round where the EnableCatchpointsWithSPContexts feature was enabled via the consensus. // we avoid generating catchpoints before that round in order to ensure the network remain consistent in the catchpoint // label being produced. This variable could be "wrong" in two cases - @@ -151,9 +151,13 @@ type catchpointTracker struct { // catchpoint files even before the protocol upgrade took place. forceCatchpointFileWriting bool - // catchpointsMu protects `roundDigest`, `reenableCatchpointsRound` and + // catchpointsMu protects roundDigest, reenableCatchpointsRound, cachedDBRound and // `lastCatchpointLabel`. catchpointsMu deadlock.RWMutex + + // cachedDBRound is always exactly tracker DB round (and therefore, accountsRound()), + // cached to use in lookup functions + cachedDBRound basics.Round } // initialize initializes the catchpointTracker structure @@ -205,7 +209,7 @@ func (ct *catchpointTracker) getSPVerificationData() (encodedData []byte, spVeri return encodedData, spVerificationHash, nil } -func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basics.Round, updatingBalancesDuration time.Duration) error { +func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basics.Round, blockProto protocol.ConsensusVersion, updatingBalancesDuration time.Duration) error { ct.log.Infof("finishing catchpoint's first stage dbRound: %d", dbRound) var totalKVs uint64 @@ -216,11 +220,15 @@ func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basic var spVerificationEncodedData []byte var catchpointGenerationStats telemetryspec.CatchpointGenerationEventDetails - // Generate the SP Verification hash and encoded data. The hash is used in the label when tracking catchpoints, - // and the encoded data for that hash will be added to the catchpoint file if catchpoint generation is enabled. - spVerificationEncodedData, spVerificationHash, err := ct.getSPVerificationData() - if err != nil { - return err + params := config.Consensus[blockProto] + if params.EnableCatchpointsWithSPContexts { + // Generate the SP Verification hash and encoded data. The hash is used in the label when tracking catchpoints, + // and the encoded data for that hash will be added to the catchpoint file if catchpoint generation is enabled. + var err error + spVerificationEncodedData, spVerificationHash, err = ct.getSPVerificationData() + if err != nil { + return err + } } if ct.enableGeneratingCatchpointFiles { @@ -257,7 +265,7 @@ func (ct *catchpointTracker) finishFirstStage(ctx context.Context, dbRound basic // Possibly finish generating first stage catchpoint db record and data file after // a crash. -func (ct *catchpointTracker) finishFirstStageAfterCrash(dbRound basics.Round) error { +func (ct *catchpointTracker) finishFirstStageAfterCrash(dbRound basics.Round, blockProto protocol.ConsensusVersion) error { v, err := ct.catchpointStore.ReadCatchpointStateUint64( context.Background(), trackerdb.CatchpointStateWritingFirstStageInfo) if err != nil { @@ -274,10 +282,10 @@ func (ct *catchpointTracker) finishFirstStageAfterCrash(dbRound basics.Round) er return err } - return ct.finishFirstStage(context.Background(), dbRound, 0) + return ct.finishFirstStage(context.Background(), dbRound, blockProto, 0) } -func (ct *catchpointTracker) finishCatchpointsAfterCrash(catchpointLookback uint64) error { +func (ct *catchpointTracker) finishCatchpointsAfterCrash(blockProto protocol.ConsensusVersion, catchpointLookback uint64) error { records, err := ct.catchpointStore.SelectUnfinishedCatchpoints(context.Background()) if err != nil { return err @@ -292,7 +300,7 @@ func (ct *catchpointTracker) finishCatchpointsAfterCrash(catchpointLookback uint } err = ct.finishCatchpoint( - context.Background(), record.Round, record.BlockHash, catchpointLookback) + context.Background(), record.Round, record.BlockHash, blockProto, catchpointLookback) if err != nil { return err } @@ -301,8 +309,8 @@ func (ct *catchpointTracker) finishCatchpointsAfterCrash(catchpointLookback uint return nil } -func (ct *catchpointTracker) recoverFromCrash(dbRound basics.Round) error { - err := ct.finishFirstStageAfterCrash(dbRound) +func (ct *catchpointTracker) recoverFromCrash(dbRound basics.Round, blockProto protocol.ConsensusVersion) error { + err := ct.finishFirstStageAfterCrash(dbRound, blockProto) if err != nil { return err } @@ -316,7 +324,7 @@ func (ct *catchpointTracker) recoverFromCrash(dbRound basics.Round) error { } if catchpointLookback != 0 { - err = ct.finishCatchpointsAfterCrash(catchpointLookback) + err = ct.finishCatchpointsAfterCrash(blockProto, catchpointLookback) if err != nil { return err } @@ -346,11 +354,15 @@ func (ct *catchpointTracker) loadFromDisk(l ledgerForTracker, dbRound basics.Rou return err } + ct.catchpointsMu.Lock() + ct.cachedDBRound = dbRound ct.roundDigest = nil + ct.consensusVersion = nil ct.catchpointDataWriting.Store(0) // keep these channel closed if we're not generating catchpoint ct.catchpointDataSlowWriting = make(chan struct{}, 1) close(ct.catchpointDataSlowWriting) + ct.catchpointsMu.Unlock() err = ct.dbs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) error { return ct.initializeHashes(ctx, tx, dbRound) @@ -359,18 +371,18 @@ func (ct *catchpointTracker) loadFromDisk(l ledgerForTracker, dbRound basics.Rou return err } - ct.accountsq, err = ct.dbs.MakeAccountsOptimizedReader() + ct.lastCatchpointLabel, err = ct.catchpointStore.ReadCatchpointStateString( + context.Background(), trackerdb.CatchpointStateLastCatchpoint) if err != nil { return } - ct.lastCatchpointLabel, err = ct.catchpointStore.ReadCatchpointStateString( - context.Background(), trackerdb.CatchpointStateLastCatchpoint) + hdr, err := l.BlockHdr(dbRound) if err != nil { return } - return ct.recoverFromCrash(dbRound) + return ct.recoverFromCrash(dbRound, hdr.CurrentProtocol) } // newBlock informs the tracker of a new block from round @@ -380,6 +392,7 @@ func (ct *catchpointTracker) newBlock(blk bookkeeping.Block, delta ledgercore.St defer ct.catchpointsMu.Unlock() ct.roundDigest = append(ct.roundDigest, blk.Digest()) + ct.consensusVersion = append(ct.consensusVersion, blk.CurrentProtocol) if (config.Consensus[blk.CurrentProtocol].EnableCatchpointsWithSPContexts || ct.forceCatchpointFileWriting) && ct.reenableCatchpointsRound == 0 { catchpointLookback := config.Consensus[blk.CurrentProtocol].CatchpointLookback @@ -396,7 +409,10 @@ func (ct *catchpointTracker) newBlock(blk bookkeeping.Block, delta ledgercore.St // number that can be removed from the blocks database as well as the lookback that this // tracker maintains. func (ct *catchpointTracker) committedUpTo(rnd basics.Round) (retRound, lookback basics.Round) { - return rnd, basics.Round(0) + ct.catchpointsMu.RLock() + defer ct.catchpointsMu.RUnlock() + retRound = ct.cachedDBRound + return retRound, basics.Round(0) } // Calculate whether we have intermediate first stage catchpoint rounds and the @@ -505,6 +521,8 @@ func (ct *catchpointTracker) prepareCommit(dcc *deferredCommitContext) error { dcc.committedRoundDigests = make([]crypto.Digest, dcc.offset) copy(dcc.committedRoundDigests, ct.roundDigest[:dcc.offset]) + dcc.committedProtocolVersion = make([]protocol.ConsensusVersion, dcc.offset) + copy(dcc.committedProtocolVersion, ct.consensusVersion[:dcc.offset]) return nil } @@ -601,6 +619,8 @@ func (ct *catchpointTracker) postCommit(ctx context.Context, dcc *deferredCommit ct.catchpointsMu.Lock() ct.roundDigest = ct.roundDigest[dcc.offset:] + ct.consensusVersion = ct.consensusVersion[dcc.offset:] + ct.cachedDBRound = dcc.newBase() ct.catchpointsMu.Unlock() dcc.updatingBalancesDuration = time.Since(dcc.flushTime) @@ -736,9 +756,18 @@ func repackCatchpoint(ctx context.Context, header CatchpointFileHeader, biggestC // Create a catchpoint (a label and possibly a file with db record) and remove // the unfinished catchpoint record. -func (ct *catchpointTracker) createCatchpoint(ctx context.Context, accountsRound basics.Round, round basics.Round, dataInfo trackerdb.CatchpointFirstStageInfo, blockHash crypto.Digest) error { +func (ct *catchpointTracker) createCatchpoint(ctx context.Context, accountsRound basics.Round, round basics.Round, dataInfo trackerdb.CatchpointFirstStageInfo, blockHash crypto.Digest, blockProto protocol.ConsensusVersion) error { startTime := time.Now() - labelMaker := ledgercore.MakeCatchpointLabelMakerCurrent(round, &blockHash, &dataInfo.TrieBalancesHash, dataInfo.Totals, &dataInfo.StateProofVerificationHash) + var labelMaker ledgercore.CatchpointLabelMaker + var version uint64 + params := config.Consensus[blockProto] + if params.EnableCatchpointsWithSPContexts { + labelMaker = ledgercore.MakeCatchpointLabelMakerCurrent(round, &blockHash, &dataInfo.TrieBalancesHash, dataInfo.Totals, &dataInfo.StateProofVerificationHash) + version = CatchpointFileVersionV7 + } else { + labelMaker = ledgercore.MakeCatchpointLabelMakerV6(round, &blockHash, &dataInfo.TrieBalancesHash, dataInfo.Totals) + version = CatchpointFileVersionV6 + } label := ledgercore.MakeLabel(labelMaker) ct.log.Infof( @@ -774,7 +803,7 @@ func (ct *catchpointTracker) createCatchpoint(ctx context.Context, accountsRound // Make a catchpoint file. header := CatchpointFileHeader{ - Version: CatchpointFileVersionV7, + Version: version, BalancesRound: accountsRound, BlocksRound: round, Totals: dataInfo.Totals, @@ -834,7 +863,7 @@ func (ct *catchpointTracker) createCatchpoint(ctx context.Context, accountsRound // Try create a catchpoint (a label and possibly a file with db record) and remove // the unfinished catchpoint record. -func (ct *catchpointTracker) finishCatchpoint(ctx context.Context, round basics.Round, blockHash crypto.Digest, catchpointLookback uint64) error { +func (ct *catchpointTracker) finishCatchpoint(ctx context.Context, round basics.Round, blockHash crypto.Digest, blockProto protocol.ConsensusVersion, catchpointLookback uint64) error { accountsRound := round - basics.Round(catchpointLookback) ct.log.Infof("finishing catchpoint round: %d accountsRound: %d", round, accountsRound) @@ -847,7 +876,7 @@ func (ct *catchpointTracker) finishCatchpoint(ctx context.Context, round basics. if !exists { return ct.catchpointStore.DeleteUnfinishedCatchpoint(ctx, round) } - return ct.createCatchpoint(ctx, accountsRound, round, dataInfo, blockHash) + return ct.createCatchpoint(ctx, accountsRound, round, dataInfo, blockHash, blockProto) } // Calculate catchpoint round numbers in [min, max]. `catchpointInterval` must be @@ -908,7 +937,9 @@ func (ct *catchpointTracker) pruneFirstStageRecordsData(ctx context.Context, max func (ct *catchpointTracker) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { if dcc.catchpointFirstStage { - err := ct.finishFirstStage(ctx, dcc.newBase(), dcc.updatingBalancesDuration) + round := dcc.newBase() + blockProto := dcc.committedProtocolVersion[round-dcc.oldBase-1] + err := ct.finishFirstStage(ctx, round, blockProto, dcc.updatingBalancesDuration) if err != nil { ct.log.Warnf( "error finishing catchpoint's first stage dcc.newBase: %d err: %v", @@ -918,8 +949,10 @@ func (ct *catchpointTracker) postCommitUnlocked(ctx context.Context, dcc *deferr // Generate catchpoints for rounds in (dcc.oldBase, dcc.newBase]. for _, round := range ct.calculateCatchpointRounds(&dcc.deferredCommitRange) { + blockHash := dcc.committedRoundDigests[round-dcc.oldBase-1] + blockProto := dcc.committedProtocolVersion[round-dcc.oldBase-1] err := ct.finishCatchpoint( - ctx, round, dcc.committedRoundDigests[round-dcc.oldBase-1], dcc.catchpointLookback) + ctx, round, blockHash, blockProto, dcc.catchpointLookback) if err != nil { ct.log.Warnf("error creating catchpoint round: %d err: %v", round, err) } @@ -1157,9 +1190,13 @@ func (ct *catchpointTracker) generateCatchpointData(ctx context.Context, account return } - err = catchpointWriter.FileWriteSPVerificationContext(encodedSPData) - if err != nil { - return + // do not write encodedSPData if not provided, + // this is an indication the older catchpoint file is being generated. + if encodedSPData != nil { + err = catchpointWriter.FileWriteSPVerificationContext(encodedSPData) + if err != nil { + return + } } for more { diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index a658cfebe4..a3a4509330 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -360,7 +360,10 @@ func createCatchpoint(t *testing.T, ct *catchpointTracker, accountsRound basics. require.Equal(t, calculateStateProofVerificationHash(t, ml), stateProofVerificationHash) - err = ct.createCatchpoint(context.Background(), accountsRound, round, trackerdb.CatchpointFirstStageInfo{BiggestChunkLen: biggestChunkLen}, crypto.Digest{}) + err = ct.createCatchpoint( + context.Background(), accountsRound, round, + trackerdb.CatchpointFirstStageInfo{BiggestChunkLen: biggestChunkLen}, + crypto.Digest{}, protocol.ConsensusCurrentVersion) require.NoError(t, err) } @@ -760,8 +763,10 @@ func TestCatchpointReproducibleLabels(t *testing.T) { // test to see that after loadFromDisk, all the tracker content is lost ( as expected ) require.NotZero(t, len(ct.roundDigest)) + require.NotZero(t, len(ct.consensusVersion)) require.NoError(t, ct.loadFromDisk(ml, ml.Latest())) require.Zero(t, len(ct.roundDigest)) + require.Zero(t, len(ct.consensusVersion)) require.Zero(t, ct.catchpointDataWriting.Load()) select { case _, closed := <-ct.catchpointDataSlowWriting: @@ -771,6 +776,56 @@ func TestCatchpointReproducibleLabels(t *testing.T) { } } +// TestCatchpointBackwardCompatibleLabels checks labels before and after EnableCatchpointsWithSPContexts was introduced. +func TestCatchpointBackwardCompatibleLabels(t *testing.T) { + partitiontest.PartitionTest(t) + + temporaryDirectory := t.TempDir() + + accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} + ml := makeMockLedgerForTracker(t, true, 10, protocol.ConsensusCurrentVersion, accts) + defer ml.Close() + + ct := &catchpointTracker{enableGeneratingCatchpointFiles: false} + conf := config.GetDefaultLocal() + + conf.Archival = true + paths := DirsAndPrefix{ + ResolvedGenesisDirs: config.ResolvedGenesisDirs{ + CatchpointGenesisDir: ".", + HotGenesisDir: ".", + }, + } + ct.initialize(conf, paths) + + defer ct.close() + ct.dbDirectory = temporaryDirectory + ct.tmpDir = temporaryDirectory + + _, err := trackerDBInitialize(ml, true, ct.dbDirectory) + require.NoError(t, err) + + err = ct.loadFromDisk(ml, ml.Latest()) + require.NoError(t, err) + + // create catpoint with the latest version of the code + round := basics.Round(2000) + + protos := []protocol.ConsensusVersion{protocol.ConsensusCurrentVersion, protocol.ConsensusV37, protocol.ConsensusV36} + labels := make([]string, len(protos)) + for i, proto := range protos { + err = ct.createCatchpoint( + context.Background(), round-1, round, + trackerdb.CatchpointFirstStageInfo{}, + crypto.Digest{}, proto) + require.NoError(t, err) + require.NotEmpty(t, ct.lastCatchpointLabel) + labels[i] = ct.lastCatchpointLabel + } + require.NotEqual(t, labels[0], labels[1]) + require.Equal(t, labels[1], labels[2]) +} + // blockingTracker is a testing tracker used to test "what if" a tracker would get blocked. type blockingTracker struct { emptyTracker diff --git a/ledger/tracker.go b/ledger/tracker.go index 7ad5ba6641..37fa6adf5f 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -269,6 +269,9 @@ type deferredCommitContext struct { // Block hashes for the committed rounds range. committedRoundDigests []crypto.Digest + // Consensus versions for the committed rounds range. + committedProtocolVersion []protocol.ConsensusVersion + // on catchpoint rounds, the transaction tail would fill up this field with the hash of the recent 1001 rounds // of the txtail data. The catchpointTracker would be able to use that for calculating the catchpoint label. txTailHash crypto.Digest From 7ebb9f4750983a35ca8426e5f6b1d8e69c286d22 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 8 Nov 2023 09:36:45 -0500 Subject: [PATCH 005/218] catchup: Provide more information to client when requested block not available (#5819) --- catchup/universalFetcher.go | 19 ++++++++++++++++--- catchup/universalFetcher_test.go | 10 +++++++--- rpcs/blockService.go | 13 +++++++++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index 0b1ab4abb0..6d9fcce8de 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -19,9 +19,9 @@ package catchup import ( "context" "encoding/binary" - "errors" "fmt" "net/http" + "strconv" "time" "github.com/algorand/go-deadlock" @@ -173,6 +173,9 @@ func (w *wsFetcherClient) requestBlock(ctx context.Context, round basics.Round) } if errMsg, found := resp.Topics.GetValue(network.ErrorKey); found { + if latest, lfound := resp.Topics.GetValue(rpcs.LatestRoundKey); lfound { + return nil, noBlockForRoundError{round: round, latest: basics.Round(binary.BigEndian.Uint64(latest))} + } return nil, makeErrWsFetcherRequestFailed(round, w.target.GetAddress(), string(errMsg)) } @@ -195,7 +198,11 @@ func (w *wsFetcherClient) requestBlock(ctx context.Context, round basics.Round) // set max fetcher size to 10MB, this is enough to fit the block and certificate const fetcherMaxBlockBytes = 10 << 20 -var errNoBlockForRound = errors.New("No block available for given round") +type noBlockForRoundError struct { + latest, round basics.Round +} + +func (noBlockForRoundError) Error() string { return "no block available for given round" } // HTTPFetcher implements FetcherClient doing an HTTP GET of the block type HTTPFetcher struct { @@ -239,7 +246,13 @@ func (hf *HTTPFetcher) getBlockBytes(ctx context.Context, r basics.Round) (data case http.StatusOK: case http.StatusNotFound: // server could not find a block with that round numbers. response.Body.Close() - return nil, errNoBlockForRound + noBlockErr := noBlockForRoundError{round: r} + if latestBytes := response.Header.Get(rpcs.BlockResponseLatestRoundHeader); latestBytes != "" { + if latest, pErr := strconv.ParseUint(latestBytes, 10, 64); pErr == nil { + noBlockErr.latest = basics.Round(latest) + } + } + return nil, noBlockErr default: bodyBytes, err := rpcs.ResponseBytes(response, hf.log, fetcherMaxBlockBytes) hf.log.Warnf("HTTPFetcher.getBlockBytes: response status code %d from '%s'. Response body '%s' ", response.StatusCode, blockURL, string(bodyBytes)) diff --git a/catchup/universalFetcher_test.go b/catchup/universalFetcher_test.go index 836360139f..c8dcbd9840 100644 --- a/catchup/universalFetcher_test.go +++ b/catchup/universalFetcher_test.go @@ -74,7 +74,9 @@ func TestUGetBlockWs(t *testing.T) { block, cert, duration, err = fetcher.fetchBlock(context.Background(), next+1, up) require.Error(t, err) - require.Contains(t, err.Error(), "requested block is not available") + require.Error(t, noBlockForRoundError{}, err) + require.Equal(t, next+1, err.(noBlockForRoundError).round) + require.Equal(t, next, err.(noBlockForRoundError).latest) require.Nil(t, block) require.Nil(t, cert) require.Equal(t, int64(duration), int64(0)) @@ -118,8 +120,10 @@ func TestUGetBlockHTTP(t *testing.T) { block, cert, duration, err = fetcher.fetchBlock(context.Background(), next+1, net.GetPeers()[0]) - require.Error(t, errNoBlockForRound, err) - require.Contains(t, err.Error(), "No block available for given round") + require.Error(t, noBlockForRoundError{}, err) + require.Equal(t, next+1, err.(noBlockForRoundError).round) + require.Equal(t, next, err.(noBlockForRoundError).latest) + require.Contains(t, err.Error(), "no block available for given round") require.Nil(t, block) require.Nil(t, cert) require.Equal(t, int64(duration), int64(0)) diff --git a/rpcs/blockService.go b/rpcs/blockService.go index a3bf886f2b..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") @@ -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 @@ -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) } From 03efd4204a94e7ba4ee163322e1285b82e3a1144 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:20:32 -0500 Subject: [PATCH 006/218] catchup: fetchAndWrite/fetchRound quit early on errNoBlockForRound (#5809) --- catchup/peerSelector.go | 40 +++++---- catchup/service.go | 46 ++++++++++- catchup/service_test.go | 81 +++++++++++++++++-- .../features/catchup/basicCatchup_test.go | 18 ----- 4 files changed, 145 insertions(+), 40 deletions(-) diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 0cdc14cf64..e9f256e1ba 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -51,6 +51,10 @@ const ( peerRank4LowBlockTime = 801 peerRank4HighBlockTime = 999 + // peerRankNoBlockForRound is used for responses failed because of no block for round + // This indicates a peer is either behind or a block has not happened yet, or does not have a block that is old enough. + peerRankNoBlockForRound = 2000 + // peerRankDownloadFailed is used for responses which could be temporary, such as missing files, or such that we don't // have clear resolution peerRankDownloadFailed = 10000 @@ -143,7 +147,7 @@ func makeHistoricStatus(windowSize int, class peerClass) *historicStats { // that will determine the rank of the peer. hs := historicStats{ windowSize: windowSize, - rankSamples: make([]int, windowSize, windowSize), + rankSamples: make([]int, windowSize), requestGaps: make([]uint64, 0, windowSize), rankSum: uint64(class.initialRank) * uint64(windowSize), gapSum: 0.0} @@ -229,18 +233,24 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera // Download may fail for various reasons. Give it additional tries // and see if it recovers/improves. - if value == peerRankDownloadFailed { + factor := float64(1.0) + switch value { + // - Set the rank to the class upper bound multiplied + // by the number of downloadFailures. + // - Each downloadFailure increments the counter, and + // each non-failure decrements it, until it gets to 0. + // - When the peer is consistently failing to + // download, the value added to rankSum will + // increase at an increasing rate to evict the peer + // from the class sooner. + case peerRankNoBlockForRound: + // for the no block errors apply very smooth rank increase + factor = 0.1 + fallthrough + case peerRankDownloadFailed: hs.downloadFailures++ - // - Set the rank to the class upper bound multiplied - // by the number of downloadFailures. - // - Each downloadFailure increments the counter, and - // each non-failure decrements it, until it gets to 0. - // - When the peer is consistently failing to - // download, the value added to rankSum will - // increase at an increasing rate to evict the peer - // from the class sooner. - value = upperBound(class) * int(math.Exp2(float64(hs.downloadFailures))) - } else { + value = upperBound(class) * int(math.Exp2(float64(hs.downloadFailures)*factor)) + default: if hs.downloadFailures > 0 { hs.downloadFailures-- } @@ -252,12 +262,12 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera // The average performance of the peer average := float64(hs.rankSum) / float64(len(hs.rankSamples)) - if int(average) > upperBound(class) && initialRank == peerRankDownloadFailed { + if int(average) > upperBound(class) && (initialRank == peerRankDownloadFailed || initialRank == peerRankNoBlockForRound) { // peerRankDownloadFailed will be delayed, to give the peer // additional time to improve. If does not improve over time, // the average will exceed the class limit. At this point, // it will be pushed down to download failed class. - return peerRankDownloadFailed + return initialRank } // A penalty is added relative to how freequently the peer is used @@ -470,7 +480,7 @@ func (ps *peerSelector) refreshAvailablePeers() { for peerIdx := len(pool.peers) - 1; peerIdx >= 0; peerIdx-- { peer := pool.peers[peerIdx].peer if peerAddress := peerAddress(peer); peerAddress != "" { - if toRemove, _ := existingPeers[pool.peers[peerIdx].class.peerClass][peerAddress]; toRemove { + if toRemove := existingPeers[pool.peers[peerIdx].class.peerClass][peerAddress]; toRemove { // need to be removed. pool.peers = append(pool.peers[:peerIdx], pool.peers[peerIdx+1:]...) } diff --git a/catchup/service.go b/catchup/service.go index be0de8b9e9..a6dbcd8b21 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -243,6 +243,8 @@ func (s *Service) innerFetch(ctx context.Context, r basics.Round, peer network.P return } +const errNoBlockForRoundThreshold = 5 + // fetchAndWrite fetches a block, checks the cert, and writes it to the ledger. Cert checking and ledger writing both wait for the ledger to advance if necessary. // Returns false if we should stop trying to catch up. This may occur for several reasons: // - If the context is canceled (e.g. if the node is shutting down) @@ -254,6 +256,11 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo if dontSyncRound := s.GetDisableSyncRound(); dontSyncRound != 0 && r >= basics.Round(dontSyncRound) { return false } + + // peerErrors tracks occurrences of errNoBlockForRound in order to quit earlier without making + // repeated requests for a block that most likely does not exist yet + peerErrors := map[network.Peer]int{} + i := 0 for { i++ @@ -302,8 +309,19 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo s.log.Infof("fetchAndWrite(%d): the block is already in the ledger. The catchup is complete", r) return false } + failureRank := peerRankDownloadFailed + if err == errNoBlockForRound { + failureRank = peerRankNoBlockForRound + // remote peer doesn't have the block, try another peer + // quit if the the same peer peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times + if count := peerErrors[peer]; count > errNoBlockForRoundThreshold { + s.log.Infof("fetchAndWrite(%d): remote peers do not have the block. Quitting", r) + return false + } + peerErrors[peer]++ + } s.log.Debugf("fetchAndWrite(%v): Could not fetch: %v (attempt %d)", r, err, i) - peerSelector.rankPeer(psp, peerRankDownloadFailed) + peerSelector.rankPeer(psp, failureRank) // we've just failed to retrieve a block; wait until the previous block is fetched before trying again // to avoid the usecase where the first block doesn't exist, and we're making many requests down the chain @@ -689,6 +707,8 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy return } + peerErrors := map[network.Peer]int{} + blockHash := bookkeeping.BlockHash(cert.Proposal.BlockDigest) // semantic digest (i.e., hash of the block header), not byte-for-byte digest peerSelector := createPeerSelector(s.net, s.cfg, false) for s.ledger.LastRound() < cert.Round { @@ -710,8 +730,30 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy return default: } + failureRank := peerRankDownloadFailed + if err == errNoBlockForRound { + failureRank = peerRankNoBlockForRound + // If a peer does not have the block after few attempts it probably has not persisted the block yet. + // Give it some time to persist the block and try again. + // None, there is no exit condition on too many retries as per the function contract. + if count, ok := peerErrors[peer]; ok { + if count > errNoBlockForRoundThreshold { + time.Sleep(50 * time.Millisecond) + } + if count > errNoBlockForRoundThreshold*10 { + // for the low number of connected peers (like 2) the following scenatio is possible: + // - both peers do not have the block + // - peer selector punishes one of the peers more than the other + // - the punoshed peer gets the block, and the less punished peer stucks. + // It this case reset the peer selector to let it re-learn priorities. + peerSelector = createPeerSelector(s.net, s.cfg, false) + } + } + peerErrors[peer]++ + } + // remote peer doesn't have the block, try another peer logging.Base().Warnf("fetchRound could not acquire block, fetcher errored out: %v", err) - peerSelector.rankPeer(psp, peerRankDownloadFailed) + peerSelector.rankPeer(psp, failureRank) continue } diff --git a/catchup/service_test.go b/catchup/service_test.go index 5282a5bc18..fc0ae38e1d 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -20,7 +20,9 @@ import ( "context" "errors" "math/rand" + "strings" "sync" + "sync/atomic" "testing" "time" @@ -180,6 +182,27 @@ func (cl *periodicSyncLogger) Warnf(s string, args ...interface{}) { cl.Logger.Warnf(s, args...) } +type periodicSyncDebugLogger struct { + periodicSyncLogger + debugMsgFilter []string + debugMsgs atomic.Uint32 +} + +func (cl *periodicSyncDebugLogger) Debugf(s string, args ...interface{}) { + // save debug messages for later inspection. + if len(cl.debugMsgFilter) > 0 { + for _, filter := range cl.debugMsgFilter { + if strings.Contains(s, filter) { + cl.debugMsgs.Add(1) + break + } + } + } else { + cl.debugMsgs.Add(1) + } + cl.Logger.Debugf(s, args...) +} + func TestSyncRound(t *testing.T) { partitiontest.PartitionTest(t) @@ -208,7 +231,7 @@ func TestSyncRound(t *testing.T) { auth := &mockedAuthenticator{fail: true} initialLocalRound := local.LastRound() - require.True(t, 0 == initialLocalRound) + require.Zero(t, initialLocalRound) // Make Service localCfg := config.GetDefaultLocal() @@ -253,7 +276,7 @@ func TestSyncRound(t *testing.T) { s.UnsetDisableSyncRound() // wait until the catchup is done waitStart = time.Now() - for time.Now().Sub(waitStart) < 8*s.deadlineTimeout { + for time.Since(waitStart) < 8*s.deadlineTimeout { if remote.LastRound() == local.LastRound() { break } @@ -298,7 +321,7 @@ func TestPeriodicSync(t *testing.T) { auth := &mockedAuthenticator{fail: true} initialLocalRound := local.LastRound() - require.True(t, 0 == initialLocalRound) + require.Zero(t, initialLocalRound) // Make Service s := MakeService(logging.Base(), defaultConfig, net, local, auth, nil, nil) @@ -315,7 +338,7 @@ func TestPeriodicSync(t *testing.T) { // wait until the catchup is done. Since we've might have missed the sleep window, we need to wait // until the synchronization is complete. waitStart := time.Now() - for time.Now().Sub(waitStart) < 10*s.deadlineTimeout { + for time.Since(waitStart) < 10*s.deadlineTimeout { if remote.LastRound() == local.LastRound() { break } @@ -506,7 +529,6 @@ func TestServiceFetchBlocksMultiBlocks(t *testing.T) { localBlock, err := local.Block(i) require.NoError(t, err) require.Equal(t, *blk, localBlock) - return } } @@ -1184,3 +1206,52 @@ func TestServiceLedgerUnavailable(t *testing.T) { require.Greater(t, local.LastRound(), basics.Round(0)) require.Less(t, local.LastRound(), remote.LastRound()) } + +// TestServiceNoBlockForRound checks if fetchAndWrite does not repeats 500 times if a block not avaialble +func TestServiceNoBlockForRound(t *testing.T) { + partitiontest.PartitionTest(t) + + // Make Ledger + local := new(mockedLedger) + local.blocks = append(local.blocks, bookkeeping.Block{}) + + remote, _, blk, err := buildTestLedger(t, bookkeeping.Block{}) + if err != nil { + t.Fatal(err) + return + } + numBlocks := 10 + addBlocks(t, remote, blk, numBlocks) + + // Create a network and block service + blockServiceConfig := config.GetDefaultLocal() + net := &httpTestPeerSource{} + ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") + + nodeA := basicRPCNode{} + nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + nodeA.start() + defer nodeA.stop() + rootURL := nodeA.rootURL() + net.addPeer(rootURL) + + require.Equal(t, basics.Round(0), local.LastRound()) + require.Equal(t, basics.Round(numBlocks+1), remote.LastRound()) + + // Make Service + auth := &mockedAuthenticator{fail: false} + cfg := config.GetDefaultLocal() + cfg.CatchupParallelBlocks = 8 + s := MakeService(logging.Base(), cfg, net, local, auth, nil, nil) + pl := &periodicSyncDebugLogger{periodicSyncLogger: periodicSyncLogger{Logger: logging.Base()}} + s.log = pl + s.deadlineTimeout = 1 * time.Second + + s.testStart() + defer s.Stop() + s.sync() + + // without the fix there are about 2k messages (4x catchupRetryLimit) + // with the fix expect less than catchupRetryLimit + require.Less(t, int(pl.debugMsgs.Load()), catchupRetryLimit) +} diff --git a/test/e2e-go/features/catchup/basicCatchup_test.go b/test/e2e-go/features/catchup/basicCatchup_test.go index 212d4cac9b..f6a009dc19 100644 --- a/test/e2e-go/features/catchup/basicCatchup_test.go +++ b/test/e2e-go/features/catchup/basicCatchup_test.go @@ -73,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 From 36050844de0aa6efe4a243e0fa30c186c09e9088 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:02:28 -0500 Subject: [PATCH 007/218] build: regenerate api routes and resolve merge error in catchup (#5821) --- catchup/service.go | 6 +- daemon/algod/api/algod.oas3.yml | 3 +- .../api/server/v2/generated/data/routes.go | 4 +- .../v2/generated/experimental/routes.go | 408 ++++++++--------- .../nonparticipating/private/routes.go | 414 +++++++++--------- .../nonparticipating/public/routes.go | 3 +- .../generated/participating/private/routes.go | 2 +- .../generated/participating/public/routes.go | 3 +- data/transactions/logic/Makefile | 2 +- 9 files changed, 425 insertions(+), 420 deletions(-) diff --git a/catchup/service.go b/catchup/service.go index a6dbcd8b21..982747ba3c 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -310,7 +310,8 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo return false } failureRank := peerRankDownloadFailed - if err == errNoBlockForRound { + var nbfe noBlockForRoundError + if errors.As(err, &nbfe) { failureRank = peerRankNoBlockForRound // remote peer doesn't have the block, try another peer // quit if the the same peer peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times @@ -731,7 +732,8 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy default: } failureRank := peerRankDownloadFailed - if err == errNoBlockForRound { + var nbfe noBlockForRoundError + if errors.As(err, &nbfe) { failureRank = peerRankNoBlockForRound // If a peer does not have the block after few attempts it probably has not persisted the block yet. // Give it some time to persist the block and try again. diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index c8c82e0d98..c4fb9394cf 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -7192,5 +7192,6 @@ { "name": "private" } - ] + ], + "x-original-swagger-version": "2.0" } \ No newline at end of file diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 9fd389f273..1ffe766db6 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -316,8 +316,8 @@ var swaggerSpec = []string{ "j9FPwV7v6kP78f0f4n5/Qbk/z50dtzU+qCwZyIYKKB92lvgXF/hvwwVsixxq93VKNJSlCs++Fnj2rSvG", "1c3j1kU2kg90yhy2wnTn51NvQIjpkN03P3T+7KpOalnrQtwEs6Dp3fqNhlqGeVir/t+nN5TpbC6kq66H", "vbWHH2ug5alrpdH7ta1ePXiCJbmDH8M8tuivp9SpG7Fnle8+H33YV3ljT53Kl3jJB5H6x635KzQnIZ9t", - "DEnv3hsuh01zHQturSPPT08xq2AplD6dfJx+6FlOwofvG8Lyvd4mlWRrLGb+/uP/DwAA//+EcjtHRPcA", - "AA==", + "DEnv3hsuh01zHQturSPPT08xq2AplD6dfJx+6FlOwofvG8Lyvd4mlWRrLGb+fjrZZEKyBeO0zJxVou0H", + "NHly8mjy8f8HAAD//3CL32ln9wAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index 1888e71ef1..3fdcd13419 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -90,210 +90,210 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "JZpUkq0x7+7Hz/83AAD//8gL9x7v8QAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 3d3c20de36..0e0d78aa42 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -139,213 +139,213 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9/XMbN7Lgv4Livip/HEfyZ3atq613ip1kdXESl6Vk7z3Ll4AzTRKrITABMBIZn//3", - "KzSAGcwMQA4lxk7qvZ9scfDRaDQa/YXuD5NcrCrBgWs1OfkwqaikK9Ag8S+a56LmOmOF+asAlUtWaSb4", - "5MR/I0pLxheT6YSZXyuql5PphNMVtG1M/+lEwq81k1BMTrSsYTpR+RJW1AysN5Vp3Yy0zhYic0Oc2iHO", - "Xk0+bvlAi0KCUkMof+DlhjCel3UBREvKFc3NJ0VumF4SvWSKuM6EcSI4EDEnetlpTOYMykId+UX+WoPc", - "BKt0k6eX9LEFMZOihCGcL8Vqxjh4qKABqtkQogUpYI6NllQTM4OB1TfUgiigMl+SuZA7QLVAhPACr1eT", - "k3cTBbwAibuVA7vG/84lwG+QaSoXoCfvp7HFzTXITLNVZGlnDvsSVF1qRbAtrnHBroET0+uIfFcrTWZA", - "KCdvv35Jnj59+sIsZEW1hsIRWXJV7ezhmmz3ycmkoBr85yGt0XIhJOVF1rR/+/VLnP/cLXBsK6oUxA/L", - "qflCzl6lFuA7RkiIcQ0L3IcO9ZsekUPR/jyDuZAwck9s44NuSjj/Z92VnOp8WQnGdWRfCH4l9nOUhwXd", - "t/GwBoBO+8pgSppB3z3KXrz/8Hj6+NHHv7w7zf7T/fn86ceRy3/ZjLsDA9GGeS0l8HyTLSRQPC1Lyof4", - "eOvoQS1FXRZkSa9x8+kKWb3rS0xfyzqvaVkbOmG5FKflQihCHRkVMKd1qYmfmNS8NGzKjOaonTBFKimu", - "WQHF1HDfmyXLlySnyg6B7cgNK0tDg7WCIkVr8dVtOUwfQ5QYuG6FD1zQHxcZ7bp2YALWyA2yvBQKMi12", - "XE/+xqG8IOGF0t5Var/LilwsgeDk5oO9bBF33NB0WW6Ixn0tCFWEEn81TQmbk42oyQ1uTsmusL9bjcHa", - "ihik4eZ07lFzeFPoGyAjgryZECVQjsjz526IMj5ni1qCIjdL0Et350lQleAKiJj9C3Jttv1/n//wPRGS", - "fAdK0QW8ofkVAZ6LAoojcjYnXOiANBwtIQ5Nz9Q6HFyxS/5fShiaWKlFRfOr+I1eshWLrOo7umarekV4", - "vZqBNFvqrxAtiARdS54CyI64gxRXdD2c9ELWPMf9b6ftyHKG2piqSrpBhK3o+u+Ppg4cRWhZkgp4wfiC", - "6DVPynFm7t3gZVLUvBgh5mizp8HFqirI2ZxBQZpRtkDiptkFD+P7wdMKXwE4fpAkOM0sO8DhsI7QjDnd", - "5gup6AICkjkiPzrmhl+1uALeEDqZbfBTJeGaiVo1nRIw4tTbJXAuNGSVhDmL0Ni5Q4dhMLaN48ArJwPl", - "gmvKOBSGOSPQQoNlVkmYggm36zvDW3xGFXzxLHXHt19H7v5c9Hd9646P2m1slNkjGbk6zVd3YOOSVaf/", - "CP0wnFuxRWZ/HmwkW1yY22bOSryJ/mX2z6OhVsgEOojwd5NiC051LeHkkj80f5GMnGvKCyoL88vK/vRd", - "XWp2zhbmp9L+9FosWH7OFglkNrBGFS7strL/mPHi7Fivo3rFayGu6ipcUN5RXGcbcvYqtcl2zH0J87TR", - "dkPF42LtlZF9e+h1s5EJIJO4q6hpeAUbCQZams/xn/Uc6YnO5W/mn6oqTW9dzWOoNXTsrmQ0HzizwmlV", - "lSynBolv3Wfz1TABsIoEbVsc44V68iEAsZKiAqmZHZRWVVaKnJaZ0lTjSP8mYT45mfzluLW/HNvu6jiY", - "/LXpdY6djMhqxaCMVtUeY7wxoo/awiwMg8ZPyCYs20OhiXG7iYaUmGHBJVxTro9alaXDD5oD/M7N1OLb", - "SjsW3z0VLIlwYhvOQFkJ2Da8p0iAeoJoJYhWFEgXpZg1P9w/raoWg/j9tKosPlB6BIaCGayZ0uoBLp+2", - "Jymc5+zVEfkmHBtFccHLjbkcrKhh7oa5u7XcLdbYltwa2hHvKYLbKeSR2RqPBiPmH4LiUK1YitJIPTtp", - "xTT+h2sbkpn5fVTnPweJhbhNExcqWg5zVsfBXwLl5n6PcoaE48w9R+S03/d2ZGNGiRPMrWhl637acbfg", - "sUHhjaSVBdB9sXcp46ik2UYW1jty05GMLgpzcIYDWkOobn3Wdp6HKCRICj0YvixFfvUPqpYHOPMzP9bw", - "+OE0ZAm0AEmWVC2PJjEpIzxe7WhjjphpiAo+mQVTHTVLPNTydiytoJoGS3PwxsUSi3rsh0wPZER3+QH/", - "Q0tiPpuzbVi/HfaIXCADU/Y4OydDYbR9qyDYmUwDtEIIsrIKPjFa915Qvmwnj+/TqD36ytoU3A65RTQ7", - "dLFmhTrUNuFgqb0KBdSzV1aj07BSEa2tWRWVkm7ia7dzjUHAhahICddQ9kGwLAtHswgR64PzhS/FOgbT", - "l2I94AliDQfZCTMOytUeuzvge+UgE3I35nHsMUg3CzSyvEL2wEMRyMzSWqtPZ0Lejh33+CwnrQ2eUDNq", - "cBtNe0jCpnWVubMZsePZBr2BWrfndi7aHz6GsQ4WzjX9HbCgzKiHwEJ3oENjQawqVsIBSH8ZvQVnVMHT", - "J+T8H6fPHz/5+cnzLwxJVlIsJF2R2UaDIvedskqU3pTwYLgyVBfrUsdH/+KZt9x2x42No0Qtc1jRajiU", - "tQhbmdA2I6bdEGtdNOOqGwBHcUQwV5tFO7HODgPaK6aMyLmaHWQzUggr2lkK4iApYCcx7bu8dppNuES5", - "kfUhdHuQUsjo1VVJoUUuyuwapGIi4l5641oQ18LL+1X/dwstuaGKmLnRFl5zlLAilKXXfDzft0NfrHmL", - "m62c3643sjo375h96SLfm1YVqUBmes1JAbN60VEN51KsCCUFdsQ7+hvQVm5hKzjXdFX9MJ8fRncWOFBE", - "h2UrUGYmYlsYqUFBLrgNDdmhrrpRx6Cnjxhvs9RpABxGzjc8R8PrIY5tWpNfMY5eILXheaDWGxhLKBYd", - "sry7+p5Ch53qnoqAY9DxGj+j5ecVlJp+LeRFK/Z9I0VdHVzI6885djnULcbZlgrT1xsVGF+U3XCkhYH9", - "KLbGz7Kgl/74ujUg9EiRr9liqQM9640UYn54GGOzxADFD1ZLLU2foa76vSgMM9G1OoAI1g7WcjhDtyFf", - "ozNRa0IJFwXg5tcqLpwlAljQc44Ofx3Ke3ppFc8ZGOrKaW1WW1cE3dmD+6LtmNHcntAMUaMSzrzGC2tb", - "2elscEQpgRYbMgPgRMycx8z58nCRFH3x2os3TjSM8IsOXJUUOSgFReYsdTtB8+3s1aG34AkBR4CbWYgS", - "ZE7lnYG9ut4J5xVsMowcUeT+tz+pB58BXi00LXcgFtvE0NvYPZxbdAj1uOm3EVx/8pDsqATi7xWiBUqz", - "JWhIoXAvnCT3rw/RYBfvjpZrkOig/F0p3k9yNwJqQP2d6f2u0NZVIh7SqbdGwjMbxikXXrCKDVZSpbNd", - "bNk06ujgZgUBJ4xxYhw4IXi9pkpbpzrjBdoC7XWC81ghzEyRBjiphpiRf/IayHDs3NyDXNWqUUdUXVVC", - "aihia+Cw3jLX97Bu5hLzYOxG59GC1Ap2jZzCUjC+Q5ZdiUUQ1Y3vyUWdDBeHHhpzz2+iqOwA0SJiGyDn", - "vlWA3TAmLAEIUy2iLeEw1aOcJhBtOlFaVJXhFjqredMvhaZz2/pU/9i2HRIX1e29XQhQGIrm2jvIbyxm", - "bTTgkiri4CAremVkDzSDWO//EGZzGDPFeA7ZNspHFc+0Co/AzkNaVwtJC8gKKOlmOOiP9jOxn7cNgDve", - "qrtCQ2bDuuKb3lKyj6LZMrTA8VRMeCT4heTmCBpVoCUQ13vHyAXg2DHm5OjoXjMUzhXdIj8eLttudWRE", - "vA2vhTY77ugBQXYcfQzACTw0Q98eFdg5a3XP/hT/AcpN0MgR+0+yAZVaQjv+XgtI2FBdxHxwXnrsvceB", - "o2wzycZ28JHUkU0YdN9QqVnOKtR1voXNwVW//gRRvyspQFNWQkGCD1YNrML+xAYk9ce8nSo4yvY2BH9g", - "fIssp2QKRZ4u8FewQZ37jY10DUwdh9BlI6Oa+4lygoD6+DkjgodNYE1zXW6MoKaXsCE3IIGoerZiWtsI", - "9q6qq0WVhQNE/RpbZnRezahPcaub9RyHCpY33IrpxOoE2+G76CkGHXQ4XaASohxhIRsgIwrBqAAYUgmz", - "68wF0/twak9JHSAd00aXdnP931MdNOMKyH+ImuSUo8pVa2hkGiFRUEAB0sxgRLBmThfq0mIISliB1STx", - "y8OH/YU/fOj2nCkyhxv/AsU07KPj4UO047wRSncO1wHsoea4nUWuD3T4mIvPaSF9nrI71MKNPGYn3/QG", - "b7xE5kwp5QjXLP/ODKB3Mtdj1h7SyLgwExx3lC+n47Ifrhv3/Zyt6pLqQ3it4JqWmbgGKVkBOzm5m5gJ", - "/tU1LX9ouuHrGsgNjeaQ5fgmZORYcGH62GckZhzGmTnANoR0LEBwZnud2047VMw2So+tVlAwqqHckEpC", - "Dvb1hJEcVbPUI2LjKvMl5QtUGKSoFy6wz46DDL9W1jQjaz4YIipU6TXP0MgduwBcMLd/QGPEKaBGpetb", - "yK0Cc0Ob+dybqTE3c7AHfY9B1Ek2nSQ1XoPU61bjtcjpvgIacRl05L0AP+3EI10piDoj+wzxFW6LOUxm", - "c38fk307dAzK4cRBqGH7MRVtaNTtcnMAoccORCRUEhReUaGZStmvYh6++HN3mNooDauhJd92/Tlx/N4m", - "9UXBS8YhWwkOm+gjd8bhO/wYPU54TSY6o8CS6tvXQTrw98DqzjOGGu+KX9zt/gnte6zU10IeyiVqBxwt", - "3o/wQO50t7spb+snpWUZcS2690B9BqCmTf4BJglVSuQMZbazQk3tQXPeSPd4qIv+N02U8wHOXn/cng8t", - "fGqKNmIoK0JJXjK0IAuutKxzfckp2qiCpUaCn7wynrZavvRN4mbSiBXTDXXJKQa+NZaraMDGHCJmmq8B", - "vPFS1YsFKN3TdeYAl9y1YpzUnGmca2WOS2bPSwUSI5CObMsV3ZC5oQktyG8gBZnVuiv943M3pVlZOoee", - "mYaI+SWnmpRAlSbfMX6xxuG8098fWQ76RsirBgvx230BHBRTWTxI6xv7FQOK3fKXLrgY0xPYzz5Ys31/", - "OzHL7Dy5/7/3//3k3Wn2nzT77VH24n8cv//w7OODh4Mfn3z8+9//X/enpx///uDf/y22Ux722GMsB/nZ", - "K6cZn71C9af1AQ1g/2T2/xXjWZTIwmiOHm2R+/jw2BHQg65xTC/hkus1N4R0TUtWGN5yG3Lo3zCDs2hP", - "R49qOhvRM4b5te6pVNyBy5AIk+mxxltLUcO4xvizR3RKupeMeF7mNbdb6aVv+6rHx5eJ+bR52mqz3pwQ", - "fPe4pD440v355PkXk2n7XrH5PplO3Nf3EUpmxTr2KrWAdUxXdAcED8Y9RSq6UaDj3ANhj4bS2diOcNgV", - "rGYg1ZJVn55TKM1mcQ7n30o4m9Oan3EbGG/OD7o4N85zIuafHm4tAQqo9DKWDaMjqGGrdjcBemEnlRTX", - "wKeEHcFR3+ZTGH3RBfWVQOeYlQG1TzFGG2rOgSU0TxUB1sOFjDKsxOin9yzAXf7q4OqQGzgGV3/Oxp/p", - "/9aC3Pvmqwty7BimumcfSNuhgyetEVXavdrqBCQZbmZzAFkh75Jf8lcwR+uD4CeXvKCaHs+oYrk6rhXI", - "L2lJeQ5HC0FO/EOwV1TTSz6QtJJpuoIneKSqZyXLyVWokLTkaVOvDEe4vHxHy4W4vHw/iM0Yqg9uqih/", - "sRNkRhAWtc5c4ohMwg2VMd+XahIH4Mg2M8y2Wa2QLWprIPWJKdz4cZ5Hq0r1HxAPl19VpVl+QIbKPY81", - "W0aUFtLLIkZAsdDg/n4v3MUg6Y23q9QKFPllRat3jOv3JLusHz16CqTzovYXd+UbmtxUMNq6knzg3Deq", - "4MKtWglrLWlW0UXMxXZ5+U4DrXD3UV5eoY2jLAl267zk9YH5OFS7AI+P9AZYOPZ+lYiLO7e9fJKw+BLw", - "E24htjHiRuv4v+1+BW97b71dvffBg12q9TIzZzu6KmVI3O9MkztoYYQsH42h2AK1VZdmaQYkX0J+5fLf", - "wKrSm2mnuw/4cYKmZx1M2cxI9mUe5uZAB8UMSF0V1InilG/6SRIUaO3Dit/CFWwuRJvaY5+sCN1H+ip1", - "UJFSA+nSEGt4bN0Y/c13UWWo2FeVf+uOjx49WZw0dOH7pA+yFXkPcIhjRNF5RJ5CBJURRFjiT6DgFgs1", - "492J9GPLM1rGzN58kSxJnvcT16RVnlwAWLgatLrb7yvANGviRpEZNXK7cBnC7EP0gIvVii4gISGHPqKR", - "z707fiUcZNe9F73pxLx/oQ3umyjItnFm1hylFDBfDKmgMtML+/MzWTek80xg4k+HsFmJYlITH2mZDpUd", - "X53NZJgCLU7AIHkrcHgwuhgJJZslVT55GeZ482d5lAzwOyZW2JZO5yyIWAsSuTXJcjzP7Z/TgXbpkur4", - "TDo+fU6oWo5IhWMkfAySj22H4CgAFVDCwi7cNvaE0iZ5aDfIwPHDfF4yDiSLBb8FZtDgmnFzgJGPHxJi", - "LfBk9AgxMg7ARvc6Dky+F+HZ5It9gOQuSQX1Y6NjPvgb4s/HbDi4EXlEZVg4S3i1cs8BqIuYbO6vXtwu", - "DkMYnxLD5q5padic0/jaQQZZXVBs7eVwcQEeD1Li7BYHiL1Y9lqTvYpus5pQZvJAxwW6LRDPxDqz70ej", - "Eu9sPTP0Ho2Qx9essYNp8+fcU2Qm1hg0hFeLjcjeAUsaDg9GoOGvmUJ6xX6p29wCs23a7dJUjAoVkowz", - "5zXkkhInxkydkGBS5HI/SIlzKwB6xo42v7RTfncqqV3xZHiZt7fatE315h8fxY5/6ghFdymBv6EVpkli", - "86YvsUTtFN3Yl27+nkCEjBG9YRNDJ83QFaSgBFQKso4QlV3FPKdGtwG8cc59t8B4gVmCKN88CAKqJCyY", - "0tAa0X2cxOcwT1JMTijEPL06Xcm5Wd9bIZpryroRsWNnmZ98BRiRPGdS6Qw9ENElmEZfK1SqvzZN47JS", - "N2TLpvJlRZw34LRXsMkKVtZxenXzfvvKTPt9wxJVPUN+y7gNWJlh6uloIOeWqW2s79YFv7YLfk0Ptt5x", - "p8E0NRNLQy7dOf4k56LHebexgwgBxohjuGtJlG5hkMED3CF3DOSmwMd/tM36OjhMhR97Z9SOfwacuqPs", - "SNG1BAaDratg6CYyYgnTQebm4cvYxBmgVcWKdc8WakdNasx0L4OHz3fXwwLurhtsBwa6cXnRMOdOrkAX", - "/edsPscoIB8bEc6GA7pYN5Co5dg3oUUt0ajWCbYbJqZsBLuRa//2p3MtJF2AM4xmFqQ7DYHL2QcNQdpH", - "RTSzHs6CzecQGgTVbYxZHeD6Zp9ocYcRRBa3GtaM6y+exchoB/W0MO5GWZxiIrSQchNdDA2vXqwK9M6m", - "ckmwNbewnkZfkH4Lm+wno6GQijKp2ogxZwnt8r89dv169S1scOSdgVgGsB27gmrqW0AajJkFm0/24USj", - "AoU5TDHpQ2cL99ip0/guHWhrXNbZNPG3YdmdrKzdpdzlYLR+OwPLmN04j7vLzOmBLuL7pLxrE1jCGBeS", - "YyByhVMx5Wv0DK+i5nn0Ltq9AFp64sXlTD5OJ3dzTsVuMzfiDly/aS7QKJ4x+Mk6Kzq+5j1RTqtKimta", - "Zs6Fl7r8pbh2lz829x6/TyxMxin74qvT128c+B+nk7wEKrNGGUuuCttVf5pV2Ty1268SlFi8VcQq68Hm", - "N8k1Q7ffzRJcMYVA3x9kfW5dusFRdG7AeTwGcyfvc95nu8QtXmioGid06yCxPuiu35leU1Z6z4SHNhEv", - "iYsblzo8yhXCAe7svw7CELKDspvB6Y6fjpa6dvAknOsHzJYW1zi4y6WGrMj5o+nBpaevhewwf/dYJurP", - "/v3EKiNkWzwmwgd9gZ6+MHVErOD1y+IXcxofPgyP2sOHU/JL6T4EAOLvM/c76hcPH0ZdDVFLgmESaCjg", - "dAUPmsDf5EZ8WrMTh5txF/Tp9aqRLEWaDBsKtY5pj+4bh70byRw+C/dLASWYn3a/rettukV3CMyYE3Se", - "ehzTxD2tbE0gRQTvh/nhuyxDWsjsVxSznlvPzfAI8XqF3o5MlSyP+4H5TBn2ym18j2lMsHHCYGZGrFki", - "XIzXLBjLNBuTxq8HZDBHFJkqmkmwxd1MuONdc/ZrDYQVRquZM5B4r/WuOq8c4KgDgdSonsO53MA2iqAd", - "/i52kDDjf19mRCC2G0HCaKIBuK8as75faOM1a3WmfYMSwxkHjHtLQKGjD0fN9oHFshsVNE6PGVMb0jM6", - "V3ogMUe01iNT2VyK3yBui0YTfuRttq9xwDAS9zcI1bOwwlmHpTQeqLZkZTv7ru0erxunNv7OurBfdFNW", - "4TaXafxU77eRt1F6VTyDqENySgkL3ZHdaNUEa8HjFcRnYUZ7H6pAuT1P9mFy59FD/FSGz4uO7fjtqXQw", - "D55klfRmRmPp/o0uZGAKtrcTVKEF8Z39Bqjm2a2dnQRBhU1bZpMbVSDb3BTDRIm31GvstKM1mlaBQYoK", - "VZepDQQrlYgMU/Mbym2ZRNPP8ivXW4H1gppeN0JiajIVj/8oIGerqDn28vJdkQ99/QVbMFsBsFYQlJhz", - "A9nqqpaKXJm+5jG5Q83ZnDyaBnUu3W4U7JopNisBWzy2LWZU4XXZeCSbLmZ5wPVSYfMnI5ova15IKPRS", - "WcQqQRrdE4W8JoppBvoGgJNH2O7xC3If47cUu4YHBotOCJqcPH6B3nf7x6PYLesqOG5j2QXy7H86nh2n", - "Ywxgs2MYJulGPYpmcbIlnNO3w5bTZLuOOUvY0l0ou8/SinK6gHjI8GoHTLYv7iZ6VHt44dYbAEpLsSFM", - "x+cHTQ1/SjxDNOzPgkFysVoxvXJRPkqsDD219ePspH44W8zUlf7wcPmPGCxX+Vihnq3rE6sxdJV4RoAh", - "jd/TFXTROiXU5qMrWRvG6gsSkTOf7hJroTQlUCxuzFxm6ShLYlTrnFSScY32j1rPs78ZtVjS3LC/oxS4", - "2eyLZ5GaIt20+3w/wD853iUokNdx1MsE2XuZxfUl97ng2cpwlOJB++w3OJXJqL54/FYqiGz70GMlXzNK", - "liS3ukNuNODUdyI8vmXAO5Jis5696HHvlX1yyqxlnDxobXbox7evnZSxEjKWw7o97k7ikKAlg2t8xBHf", - "JDPmHfdClqN24S7Qf94QFC9yBmKZP8tRRSDwaG57v2mk+J++a5PxomPVPo7p2QCFjFg7nd3uEwd87Wd1", - "6/tvbcwOfktgbjTabKX3AVYSobo2Frfp84mf80bNvXbPOwbHx78QaXRwlOMfPkSgHz6cOjH4lyfdz5a9", - "P3wYz4kZNbmZX1ss3EUjxr6xPfxSRAxgvgBVE1DknuxGDJCpS8p8MExw5oaakm6xn08vRRzmMUg84C9+", - "Ci4v3+EXjwf8o4+Iz8wscQPbkOb0Ye8WO4uSTNF8D0KNKflSrMcSTu8O8sTzB0BRAiUjzXO4kkExt6i7", - "fme8SECjZtQZlMIomWGditCe/+fBs1n8dAu2a1YWP7XphnoXiaQ8X0YDNWem489t0fVmiZZVRlPfLynn", - "UEaHs7rtz14Hjmjp/xJj51kxPrJtv5igXW5vcS3gXTA9UH5Cg16mSzNBiNVuJpfmpXC5EAXBedo86y1z", - "HFblDEqF/VqD0rGjgR/sayV0dhnmaytVEeAFWr+OyDeYU8HA0kmii1Ynn56wm6qrrkpBiymmTbz46vQ1", - "sbPaPrZ0sK2UtUCjS3cVUSv5+NRlTRXg+Jv88eNsfyRsVq101hS2imU9Mi3a0lusFzqB5pgQO0fklbWE", - "KW9nsZMQTL4pV1AEdbSsLoY0Yf6jNc2XaGLqXGRpkh9f4s1TZWuAD+pFN3UV8NwZuF2VN1vkbUqEXoK8", - "YQrwFSZcQzfRUpN1zJk4feKl7vJkzbmllKM9ZIqmisK+aPfAWYHE+4ajkPUQv6eBwVZI3Lfi3Tn2iqZ5", - "7pfP6zlvfdqepg7wd85GnFMuOMsxyXJMIMKkMOO8TSPyUcfdRGriTmjkcEWL9jXvvxwWk2X8PCN0iBt6", - "boOvZlMtddg/NaxdMZcFaOU4GxRTX3vS+TUYV+DqZBgiCvmkkJHYlGg8e+MH35OMMN9DwlD1tfn2vTNj", - "4kPoK8bRYOHQ5sRs63koFUMHIydMk4UA5dbTTXql3pk+R5j/qYD1+6PXYsHyc7bAMWw0lFm2Df0bDnXq", - "AwFd4J1p+9K0dVl5m587UT120tOqcpOmK5PGyzGveRLBsfATHw8QILcZPxxtC7ltjeDF+9QQGlxj8BFU", - "eA8PCKOp0tkriW1UBEtR2ILYt0nR1HyMR8B4zbj3hMUviDx6JeDG4HlN9FO5pNqKgKN42gXQMhHHjm/9", - "rCv1rkP1cxIblOAa/RzpbWwLjCYYR9OgFdwo3xB/KAx1B8LES1o2EbCRcqEoVTkhqsA3Ir0CojHGYRi3", - "L1HcvQB2VCWftt0xz/e+N1Eq+9GsLhagM1oUsbIlX+JXgl/9Wx9YQ1435S2qiuSY7LOb/XRIbW6iXHBV", - "r7bM5RvccbqgIm+EGsKqwH6HMbvCbIP/7lMvvol93ft9mw90LfZL+Tt8rxeTeg1NZ4otsvGYwDvl7uho", - "p74dobf9D0rppVh0AfkcRtIElwv3KMbfvjIXR5gScBBmbK+WJmMfhvQK/O6TXDS5prpcCa+yQQUTdF43", - "ddq3myHSFdenePkl3pSGJm97v1ozcOplaZ58CE21S8miKdnKgpJpLmzIZ8+IPvQEpcI8bZTn4YzPbq1b", - "EZp2wXzbcbjYUJ+WWSQdLbfzhbQbvK8z5Nvr1GNjnwEcv/crMl+By9NWSbhmovZBND6U1auE9tdOfePm", - "uXd0/dEA8c9tfE6ayi9cZTy7TKeTf/uTdaYR4Fpu/gCG88GmD2o9D6Vda55qm5CmqNKoIkudW3FMdvxY", - "InYnG3aqTe+olT0gq1djxIFh7evp5KzY68KMJfOf2FFixy5eyTqd67jNb4xHrBKKtbXNYiWuR8aMX2CV", - "6iBX83AsH0t4DbnGgnZtjJQE2Cdzs5nM2+7/O+dxWp1uQutdquNt+Y2HVex23PGDFCRBGh1bAexofDbf", - "0yYS1j7kuaEKc99LtHF3n76OfoA3n0Ou2fWOlC//XAIP0olMvV0GYZkHGWBY8xwFM4bub3VsAdqWkWUr", - "PEHm/juDk3qOfAWbe4p0qCFakqx5i3WbZJGIAeQOmSERoWKRZtaQ7IJ/mGooA7HgIzttd2jTbierGQcJ", - "jG45lydJc3G0SY22TBkvpzpqLtN1r1Rf+LIilRVmWI0xrX+8wuKXysU50SbZZKilk7NhSv4bl6wSE/Q0", - "vhOfthKU/81n47KzlOwKwnrL6Km6obLwLaKmF2/VybbcR4NULr6SYB/oeTMza+Pwh77qSJJnfNKSl8KI", - "EVnqXVA39L2JG7unbIBfm4cF4ZqDdHXpUf4thYJMCx+3vw2ObaiwUYy3QoJKFlawwCXTnb5t87ligRmK", - "6U2pC14MF0gkrKiBTgZZV9NzbkP2S/vdv6X2BUZ2Wpgaet1d6c6/wGBqgMSQ6ufE3Za732jfxtjEOAeZ", - "ec9TPwUrB9n1hlRSFHVuL+jwYDQGudEpULawkqidJh+usqcjBG+dr2BzbJUgXyLQ72AItJWcLOhB6r7e", - "Jh/U/KZicC8OAt7ntFxNJ5UQZZZwdpwN88b2Kf6K5VdQEHNT+EjlRPVXch9t7I03+2a58XlSqwo4FA+O", - "CDnl9m2Id2x3Cxf1Juf39Lb51zhrUdtUzs6odnTJ40H2mGRZ3pGb+WG28zAFhtXdcSo7yI6spOtEzlpJ", - "byK1kI/GauVDV3O/Pm1LVBaKmExybj1WL/GgxwxH+JI9SLmAjkxKnKeLqFLEQjJv89reDBXHVDgZAqSB", - "j3n03UDhBo8iIFpxNXIKbQYzl7tMzImE1ol82yRuw+KwMY2+P3MzS5ffzYWETplX01vIwos8TLX1mKmc", - "MS2p3Nwm1dqgOO3AepLE8s5wrCYSq11IG401xGFZipsMmVXW5DaPqbamnepexr6cS9vPnOoZBHFdVDlB", - "bUOWtCC5kBLysEf82Z6FaiUkZKXAMK+YB3qujdy9wrc6nJRiQUSViwJsjYA4BaXmqjmnKDZBEFUTRYGl", - "HXz0afsEdDxyykNVRrbJeeyiM+vLTASegnLJeByGbOMhvFuqCu+Vnf9sjhYhhrEu3bfXVvoMayvDnqWV", - "WVl6g0GqujL5UdUYjoQPb8wUz8hKKO00OzuSaoZqQ7zu54JrKcqyawSyIvHCWba/o+vTPNevhbia0fzq", - "AeqRXOhmpcXUP0vtB+O1M8leRqaRZaAvlhE7L87iT93etZ4d59i7RGsA5vvdHGu3jfs0Vsq6u65+bXae", - "yJ2pxYrlcRr+c0W3JWPSYiwhmurJVkmyj/OxGTLq8HJoghmQJQ3RDNwQbGy/HE9zTl1kHua/KPH2xyVz", - "cJdE4mIa8kkntWR5UrbqAYCQ2hejupa2tFIo+TRcRSzsC3N0SfcBHcnFMfLnbrCZEQ4OlIY7ATWINmwA", - "vG+V/alNyWUjF2di7b8/aHN23Qr4j9upPFaOPnKKG9Jy1fJ9fo8ER4hnBt4af4SFw/0NujsKqSmDN/JG", - "DQBIxyV1YBgVnbQvGHPKSigyqhOXO9qEpoFm61609IubMuU4eU7thb0EYsauJbh8E1ak7hVDr6ghJdE0", - "H1pueQFrUJgMwlZ0psr6Gby/A0pbVqqnfIsqK+EaOuFaLglGjaIduwbfVzWdSQFQofevb5OKxSGFd3nP", - "UOHWngWRLGOwG7VcWMTanSI7zBJRI8qaZ/aYqLFHyUB0zYqadvCn9hU5umY3c5QjqBrI5JnX28ZO86Md", - "4a0f4NT3j4kyHhPvx/GhvVlQHHXbGNDOuMRapU49j4clhhleGocGzlY0jk9L4i3fUBW94WkD4JDkW/Vm", - "5D4xwQPEfrWGHKWabtzd3XFCcDCietmbkiK4bHb49obkz0LDW0k4OV5M1VCADHarpcbThRPYsQGWs+RG", - "7DVSM5aQcvzf8b8pVuC3Axm92la0CjW4V+A9dphQunFWOIGWNReajy+cunyCfaWcBZHVK7ohQuI/Rl/7", - "taYlm2/whFrwfTeiltSQkHMRWt+1i1c0E28XTKYeMG8XEH4qu242dsxguI0ZJQDaXIHOOIWZga4g3AZ0", - "y1vOk2vDclQ9WzGl8LLrbecQC27xPifEihahjoyZ6bqlRH2uUtP7f7avtsKpfEKpqqS5r18GRNFVzyBu", - "axR64tJLWG1/1jdUjz0JNHUPW6KV/jlvcQvj3p6RG7FY+VS9hw7Yg3pwg1IXd1rGPgWK25fRWx5EjlrK", - "oXdhbHzIAGh0MvusXjvAt9kYfQawT4H/aNLI1DLGgP9HwXuijF4Ir62Y9wmw3HnyH4HV2lVnYp1JmKtd", - "oRDWsGoUYdkmC/DGScZzCVTZ2JCzH5zK1uZEZNyokDZ6sfG+NaMUMGe8ZZaMV7WOaACYGpFvAoSF5mlE", - "a8LZk5ISjBh2TcsfrkFKVqQ2zpwOW8YrzEnvTfKub0T5b+7U4QBMtdoPviSE9qVa0Mxc4LbqjQ0sVJry", - "gsoibM44yUGae5/c0I26ve/DQCtrI1/s8H7QQJrpvm8P/CBI2haQcuPcl3f0TDQA0gO6KEa4FjCCNeJW", - "sEYRLRKehCEM8bQKdJ2VYoHvyxIE6JJPou/HKiuCo8HWykP7zaPYb7B9Gsy77Q6+FjjrmCm2n7MfEHWo", - "8PzImd560qw1rf/gz0Zk2oPg6Z8v2rBwuzlD+o+90bzARwydd5r9ovN+r214iJ0PEp6MrgU3sYvoIHcP", - "fENz7fh6Rl0ffOwlqNVhM9Rt1ZbAb1BtkDPNXeDO0OgzUIotUqbuHe2eNiFrSfb3QAI8W6nWna3utE0w", - "hRlnnyJQ21/OZpWosnxMNKBNzV84g7aDtAtjgj4Cc3Vi3U3ghGqKVXQSm3SqVuxbBytZNWOXX6bKtynZ", - "KYNGgoN2jeVijrwMj7A14+Abj8Z4Me2/PuoabBomQSiRkNcSDZo3dLO7rlAiJez5P06fP37y85PnXxDT", - "gBRsAapNK9yry9NGjDHet7N82hixwfJ0fBP8u3SLOO8p889tmk1xZ81yW9XmDBxUJdrHEhq5ACLHMVIP", - "5lZ7heO0Qd9/rO2KLfLgOxZDwe+zZy6yNb6AU+70FzEn23lGt+afjvMLI/xHLim/tbdYYMoem34XfRt6", - "bA2yfxgqjDz0PhjtNcv9PSguKmXernzuKNCGj34j5IEAJF7zdd5hhdW123yV0tp20QrsHWb9S+y71pG2", - "M+wcIfEddoAXPs9r2zWR0g6cz5z48bsGKcFS3qcoobP8XS/+3AJbz2OwRU7V1RqUZUtiKFwEzznVy+aV", - "ZEK2HTymxFLaRr8py8gjTKt945kKCccIlvKalp+ea2CN9VPEBxRv008vwpd4IZItKtXt8oC9pqPmDl7d", - "HW5q/gYffv4TzB5F7zk3lHM6Dm4ztJ1gYeOFvxXsW1Jyg2PaoJLHX5CZy8leSciZ6jszrccpiAq8Bsnm", - "LoAP1nrHS7dd6/xJ6DuQ8dxHHpDvA6eEQONPC2F7RD8zU0mc3CiVx6hvQBYR/MV4VFjDccd1ccf83bdL", - "KxEkiNozrcSwOuXY5dnUCebSqRUM1zn6tu7gNnJRt2sbmxNldBrwy8t3ejYmlUk8ZbfpjrlUDpK7e6/M", - "3b9DFhWLIzeGmzdGMT+l8mra3JGJFK69/ahZuTPMoJOQ9+N0sgAOiilMOfuzKzHwae9SD4F92T08qhbW", - "u6SjsIiJrLUzeTBVkGp3RJZd1y2SUxdfTeW1ZHqD5SW9GYb9HM338k2TO8Dlnmg8IO7u0+IKmhK/baaB", - "Wvnb9RtBS7yPrGOGm1tIlEfkqzVdVaUzKpK/35v9FZ7+7Vnx6Onjv87+9uj5oxyePX/x6BF98Yw+fvH0", - "MTz52/Nnj+Dx/IsXsyfFk2dPZs+ePPvi+Yv86bPHs2dfvPjrPcOHDMgWUJ8B+mTyf7LTciGy0zdn2YUB", - "tsUJrdi3YPYGdeW5wPJnBqk5nkRYUVZOTvxP/8ufsKNcrNrh/a8TV8ZjstS6UifHxzc3N0dhl+MFPi3O", - "tKjz5bGfB4tSdeSVN2dNTLKNnsAdbW2QuKmOFE7x29uvzi/I6Zuzo5ZgJieTR0ePjh67CqicVmxyMnmK", - "P+HpWeK+Hztim5x8+DidHC+BlpiJw/yxAi1Z7j9JoMXG/V/d0MUC5BGGndufrp8ce7Hi+IN7Yv1x27fj", - "0DF//KHzEr3Y0ROdyscffB3E7a07NfBcPE/QYSQU25odz7D2wdimoILG6aWgsqGOP6C4nPz92Nk84h9R", - "bbHn4dina4i37GDpg14bWHf0WLMiWElOdb6sq+MP+B+k3o+WnZQQS91gc3JT0jafEqYJnQmJlfN0vjQc", - "xJfsYipoGRbSPSvMMTC9XloIfAVU9NJOTt4NA9BxIOJHQp5hDkR7pDsztVwbHZxBnf/mTuq0b2+md4+y", - "F+8/PJ4+fvTxL+bmcX8+f/px5FuNl8245Ly5VkY2fI/1rjAqDU/6k0ePPHtzykNAmsfuJAeLGyhR7SLt", - "JjVBb8Nb39FCOsDYbVVvINIgY0ddnt7wQ+EFOfqzPVe81dLUSTSIw/cLIRTEv4vEuR9/urnPuA21MzeH", - "veE+TifPP+Xqz7gheVoSbBkUWhxu/Y/8iosb7lsacaRerajc+GOsOkyBuM3GS48uFDq+JLumKAVywYPs", - "SXwxeY/v8GNvUxP8Rml6C35zbnr9N7/pNIwX2rbmD1eUM3DX2sukqUECPqWcD9GkxTXluY8Gb4NMcb+s", - "wOsIo4ljqhXM69K/O65KNre1ToUo/USqrirDceZUNZTlIluNBGufcTZDk5rngluPOAYR+1yK+BwTH26q", - "K1Z1urC5oSpXhZMDuJd6uOm/1iA37a6vmBFF2+0dxGz8nizc4vEALLw70IFZ+JM92eiff8X/tS+tZ4/+", - "9ukg8NkKLtgKRK3/rJfmub3B7nRpOhneJtw+1mt+jFFyxx86Gon7PNBIur+33cMW1ytRgFchxHxuq7hv", - "+3z8wf4bTATrCiRbAbflVN2v9uY4xmKem+HPG55Hfxyuo5OIMfHzsTdxxLTcbssPnT+7yp1a1roQN7b+", - "VFReweuTlq7eMlryG6uAuQfdAG2OSPJD1VxULvEBoVhvR9S6NdvYiGD3orBxrOGNppbOg7FgHCdADwnO", - "YguL0+ACV2DuRjRG9GQjB9n3ooChbBS7CB2MncuwOQqRMt53vhiHjPfjfgcFPTnWDTkkI/OxVv2/j28o", - "00aCcskaEaPDzhpoeewqs/R+bZOhD75ghvfgx/BZZPTXY9o9F10jidmyVMeBBSX21VkQEo18TLL/3FpT", - "Q+skkktjl3z33uw61mB2lNQa206Oj/GRylIofYySaNcQF35832y0Lx3YbPjH9x//fwAAAP//APnWz5P5", - "AAA=", + "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", + "x0cnk4//PwAA///7V+betvkAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 89202c3fb1..6d7d2c4177 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -1003,7 +1003,8 @@ var swaggerSpec = []string{ "jWPwgjM6uyeorNGTauAeNEqO9inp41GvoG2QuqpD5yxymmxmgBTRkAcC/NQTH6JG6y3R3xL9l070sZKK", "iLpZy1ph8RVuyzWbta67gOgNWsk+SXXh2xL9f/YS/Z4DKUKJpA0dJN4bjirCNLnAskhTIOb+KtE67xru", "OX0dM+2Co+4qbSrXni9dUMZdTZ0qrwHh0K5bvPbtaa/FsGmZGVo0DTogLSXTG9RaaMF+Pwfz/3dG7Fcg", - "V16hKWU+ejpaaF08PTrKRUrzhVD6aPRxHD5TrYfvKvg/eF2kkGxl9KuP7z7+3wAAAP//BgzyFgmnAQA=", + "V16hKWU+ejpaaF08PTrKRUrzhVD6aPRxHD5TrYfvKvg/eF2kkGxl9KuPCLaQbM64uXMv6HwOsjYhjh5N", + "How+/t8AAAD//0RubYkspwEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 79a65f5721..f352c24c7e 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -412,7 +412,7 @@ var swaggerSpec = []string{ "tc+EfYYjxlXBP4RrfGqDXRRX1l5HOYENsxFskQ08rg3vT5b3J8v712F5Z/sZTVcwubPV6xq2K1o1ti61", "rHUhbgIPN8Jio0+HPj6r+Pf/Pr2hTGdzIV2NfjrXIIcfa6DlqWvI2fu17YE1eIKNvYIfw2o40V9Paddp", "2fWNG9ab+nDgOI89dY7jxEs+FdU/boNowqAUZPtNOMq794ZlK5BrfyO0MRYvTk+xNsFSKH06+Tj90Iu/", - "CB++b8jjQ3OPODL5+P7j/wsAAP//bjFnIYoHAQA=", + "CB++b8jjQ3OPODL5iHQhJFswTsvMxTa0XYUnT04eTT7+vwAAAP//tpMWzK0HAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 67e59f47b8..107ad4eba1 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -391,7 +391,8 @@ var swaggerSpec = []string{ "kO1XO4f9X/W4r3sV1DGJGEuT+BpJRFXzOUuZRXku+ILQhWgioLESJBf4BKQBzvahIUxPXa8n5opKujbV", "7XrNbcm9LwGcNVu4M2qgQy7xgAFDeHtGC/zbmFCB/9FS+k2LAd2WkW4du8dV/+Qqn4KrfHa+8kf3wwam", "xf+WYuaT4yd/2AWFhujvhSbfYHT/7cSxuvV/rB3PTQUtX2fDm/uaCOEw4hZv0TrW9t17cxEokCt/wTYB", - "pCdHR1h4aSmUPpqY668dXBo+fF/D/MHfTqVkK+z3+v7j/w8AAP//4/5pcmcQAQA=", + "pCdHR1h4aSmUPpqY668dXBo+fF/D/MHfTqVkK+z3itZNIdmCcZonLnAzaYJEHx0eTz7+/wAAAP//5fUK", + "SIoQAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/data/transactions/logic/Makefile b/data/transactions/logic/Makefile index 4ed18ecbc1..21d72a71ca 100644 --- a/data/transactions/logic/Makefile +++ b/data/transactions/logic/Makefile @@ -19,7 +19,7 @@ fields_string.go: fields.go go generate README.md: TEAL_opcodes.md README_in.md - python merge.py > README.md + python3 merge.py > README.md @if [ -e $(SPECS)/dev/TEAL.md ]; then \ sed '/^$$/q' $(SPECS)/dev/TEAL.md | cat - README.md > teal.spec; \ mv teal.spec $(SPECS)/dev/TEAL.md; \ From 8e30dd4f17d68664f84a13a5dabc615d1b8c092e Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 9 Nov 2023 15:15:32 -0500 Subject: [PATCH 008/218] txHandler: applications rate limiter (#5734) * Rate limit incoming apps based on app id + sender IP addr when the backlog is more than 1/2 full. * Implemented as a sharded map with sliding window limiter. The sliding window data is used to for least recently used eviction (list based). * Two hash functions are used: 1) memhash64 for app to bucket mapping 2) salted blake2b for app id + sender caching (shrank to 8 bytes to save on memory) --- config/consensus.go | 5 + config/localTemplate.go | 18 +- config/local_defaults.go | 6 +- data/appRateLimiter.go | 322 ++++++++++++++++ data/appRateLimiter_test.go | 526 ++++++++++++++++++++++++++ data/txHandler.go | 78 ++-- data/txHandler_test.go | 178 +++++++++ installer/config.json.example | 6 +- network/p2pPeer.go | 10 + network/wsPeer.go | 46 +++ network/wsPeer_test.go | 47 +++ test/testdata/configs/config-v32.json | 139 +++++++ util/metrics/metrics.go | 2 + 13 files changed, 1353 insertions(+), 30 deletions(-) create mode 100644 data/appRateLimiter.go create mode 100644 data/appRateLimiter_test.go create mode 100644 test/testdata/configs/config-v32.json diff --git a/config/consensus.go b/config/consensus.go index c3592e40d9..25b5dc7858 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -633,6 +633,9 @@ var StateProofTopVoters int // in a block must not exceed MaxTxnBytesPerBlock. var MaxTxnBytesPerBlock int +// MaxAppTxnForeignApps is the max number of foreign apps per txn across all consensus versions +var MaxAppTxnForeignApps int + func checkSetMax(value int, curMax *int) { if value > *curMax { *curMax = value @@ -681,6 +684,8 @@ func checkSetAllocBounds(p ConsensusParams) { checkSetMax(p.MaxAppKeyLen, &MaxAppBytesKeyLen) checkSetMax(int(p.StateProofTopVoters), &StateProofTopVoters) checkSetMax(p.MaxTxnBytesPerBlock, &MaxTxnBytesPerBlock) + + checkSetMax(p.MaxAppTxnForeignApps, &MaxAppTxnForeignApps) } // SaveConfigurableConsensus saves the configurable protocols file to the provided data directory. diff --git a/config/localTemplate.go b/config/localTemplate.go index 74c0b2e08a..7e92d5ed7a 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -42,7 +42,7 @@ type Local struct { // Version tracks the current version of the defaults so we can migrate old -> new // This is specifically important whenever we decide to change the default value // for an existing parameter. This field tag must be updated any time we add a new version. - Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31"` + Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32"` // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. Relays (nodes with a valid NetAddress) are always Archival, regardless of this setting. This may change in the future. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only effects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. Archival bool `version[0]:"false"` @@ -231,7 +231,21 @@ type Local struct { // TxBacklogReservedCapacityPerPeer determines how much dedicated serving capacity the TxBacklog gives each peer TxBacklogReservedCapacityPerPeer int `version[27]:"20"` - // EnableTxBacklogRateLimiting controls if a rate limiter and congestion manager shouild be attached to the tx backlog enqueue process + // TxBacklogAppTxRateLimiterMaxSize denotes a max size for the tx rate limiter + // calculated as "a thousand apps on a network of thousand of peers" + TxBacklogAppTxRateLimiterMaxSize int `version[32]:"1048576"` + + // TxBacklogAppTxPerSecondRate determines a target app per second rate for the app tx rate limiter + TxBacklogAppTxPerSecondRate int `version[32]:"100"` + + // TxBacklogRateLimitingCongestionRatio determines the backlog filling threshold percentage at which the app limiter kicks in + // or the tx backlog rate limiter kicks off. + TxBacklogRateLimitingCongestionPct int `version[32]:"50"` + + // EnableTxBacklogAppRateLimiting controls if an app rate limiter should be attached to the tx backlog enqueue process + EnableTxBacklogAppRateLimiting bool `version[32]:"true"` + + // EnableTxBacklogRateLimiting controls if a rate limiter and congestion manager should be attached to the tx backlog enqueue process // if enabled, the over-all TXBacklog Size will be larger by MAX_PEERS*TxBacklogReservedCapacityPerPeer EnableTxBacklogRateLimiting bool `version[27]:"false" version[30]:"true"` diff --git a/config/local_defaults.go b/config/local_defaults.go index 12689e4e9b..06a26f2c1c 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -20,7 +20,7 @@ package config var defaultLocal = Local{ - Version: 31, + Version: 32, AccountUpdatesStatsInterval: 5000000000, AccountsRebuildSynchronousMode: 1, AgreementIncomingBundlesQueueLength: 15, @@ -81,6 +81,7 @@ var defaultLocal = Local{ EnableRequestLogger: false, EnableRuntimeMetrics: false, EnableTopAccountsReporting: false, + EnableTxBacklogAppRateLimiting: true, EnableTxBacklogRateLimiting: true, EnableTxnEvalTracer: false, EnableUsageLog: false, @@ -141,6 +142,9 @@ var defaultLocal = Local{ TrackerDBDir: "", TransactionSyncDataExchangeRate: 0, TransactionSyncSignificantMessageThreshold: 0, + TxBacklogAppTxPerSecondRate: 100, + TxBacklogAppTxRateLimiterMaxSize: 1048576, + TxBacklogRateLimitingCongestionPct: 50, TxBacklogReservedCapacityPerPeer: 20, TxBacklogServiceRateWindowSeconds: 10, TxBacklogSize: 26000, diff --git a/data/appRateLimiter.go b/data/appRateLimiter.go new file mode 100644 index 0000000000..b7684409a8 --- /dev/null +++ b/data/appRateLimiter.go @@ -0,0 +1,322 @@ +// 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 data + +import ( + "encoding/binary" + "sync" + "sync/atomic" + "time" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "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/util" + "github.com/algorand/go-deadlock" + "golang.org/x/crypto/blake2b" +) + +const numBuckets = 128 + +type keyType [8]byte + +// appRateLimiter implements a sliding window counter rate limiter for applications. +// It is a sharded map with numBuckets of maps each protected by its own mutex. +// Bucket is selected by hashing the application index with a seed (see memhash64). +// LRU is used to evict entries from each bucket, and "last use" is updated on each attempt, not admission. +// This is mostly done to simplify the implementation and does not look affecting the correctness. +type appRateLimiter struct { + maxBucketSize int + serviceRatePerWindow uint64 + serviceRateWindow time.Duration + + // seed for hashing application index to bucket + seed uint64 + // salt for hashing application index + origin address + salt [16]byte + + buckets [numBuckets]appRateLimiterBucket + + // evictions + // TODO: delete? + evictions uint64 + evictionTime uint64 +} + +type appRateLimiterBucket struct { + entries map[keyType]*appRateLimiterEntry + lru *util.List[keyType] + mu deadlock.RWMutex // mutex protects both map and the list access +} + +type appRateLimiterEntry struct { + prev atomic.Int64 + cur atomic.Int64 + interval int64 // numeric representation of the current interval value + lruElement *util.ListNode[keyType] +} + +// makeAppRateLimiter creates a new appRateLimiter from the parameters: +// maxCacheSize is the maximum number of entries to keep in the cache to keep it memory bounded +// maxAppPeerRate is the maximum number of admitted apps per peer per second +// serviceRateWindow is the service window +func makeAppRateLimiter(maxCacheSize int, maxAppPeerRate uint64, serviceRateWindow time.Duration) *appRateLimiter { + // convert target per app rate to per window service rate + serviceRatePerWindow := maxAppPeerRate * uint64(serviceRateWindow/time.Second) + maxBucketSize := maxCacheSize / numBuckets + if maxBucketSize == 0 { + // got the max size less then buckets, use maps of 1 + maxBucketSize = 1 + } + r := &appRateLimiter{ + maxBucketSize: maxBucketSize, + serviceRatePerWindow: serviceRatePerWindow, + serviceRateWindow: serviceRateWindow, + seed: crypto.RandUint64(), + } + crypto.RandBytes(r.salt[:]) + + for i := 0; i < numBuckets; i++ { + r.buckets[i] = appRateLimiterBucket{entries: make(map[keyType]*appRateLimiterEntry), lru: util.NewList[keyType]()} + } + return r +} + +func (r *appRateLimiter) entry(b *appRateLimiterBucket, key keyType, curInt int64) (*appRateLimiterEntry, bool) { + b.mu.Lock() + defer b.mu.Unlock() + + if len(b.entries) >= r.maxBucketSize { + // evict the oldest entry + start := time.Now() + + el := b.lru.Back() + delete(b.entries, el.Value) + b.lru.Remove(el) + + atomic.AddUint64(&r.evictions, 1) + atomic.AddUint64(&r.evictionTime, uint64(time.Since(start))) + } + + entry, ok := b.entries[key] + if ok { + el := entry.lruElement + // note, the entry is marked as recently used even before the rate limiting decision + // since it does not make sense to evict keys that are actively attempted + b.lru.MoveToFront(el) + + // the same logic is applicable to the intervals: if a new interval is started, update the entry + // by moving the current value to the previous and resetting the current. + // this is done under a lock so that the interval is not updated concurrently. + // The rationale is even this requests is going to be dropped the new interval already started + // and it is OK to start a new interval and have it prepared for upcoming requests + var newPrev int64 = 0 + switch entry.interval { + case curInt: + // the interval is the same, do nothing + case curInt - 1: + // these are continuous intervals, use current value as a new previous + newPrev = entry.cur.Load() + fallthrough + default: + // non-contiguous intervals, reset the entry + entry.prev.Store(newPrev) + entry.cur.Store(0) + entry.interval = curInt + } + } else { + el := b.lru.PushFront(key) + entry = &appRateLimiterEntry{interval: curInt, lruElement: el} + b.entries[key] = entry + } + return entry, ok +} + +// interval calculates the interval numeric representation based on the given time +func (r *appRateLimiter) interval(nowNano int64) int64 { + return nowNano / int64(r.serviceRateWindow) +} + +// fraction calculates the fraction of the interval that is elapsed since the given time +func (r *appRateLimiter) fraction(nowNano int64) float64 { + return float64(nowNano%int64(r.serviceRateWindow)) / float64(r.serviceRateWindow) +} + +// shouldDrop returns true if the given transaction group should be dropped based on the +// on the rate for the applications in the group: the entire group is dropped if a single application +// exceeds the rate. +func (r *appRateLimiter) shouldDrop(txgroup []transactions.SignedTxn, origin []byte) bool { + return r.shouldDropAt(txgroup, origin, time.Now().UnixNano()) +} + +// shouldDropAt is the same as shouldDrop but accepts the current time as a parameter +// in order to make it testable +func (r *appRateLimiter) shouldDropAt(txgroup []transactions.SignedTxn, origin []byte, nowNano int64) bool { + keysBuckets := txgroupToKeys(txgroup, origin, r.seed, r.salt, numBuckets) + defer putAppKeyBuf(keysBuckets) + if len(keysBuckets.keys) == 0 { + return false + } + return r.shouldDropKeys(keysBuckets.buckets, keysBuckets.keys, nowNano) +} + +func (r *appRateLimiter) shouldDropKeys(buckets []int, keys []keyType, nowNano int64) bool { + curInt := r.interval(nowNano) + curFraction := r.fraction(nowNano) + + for i, key := range keys { + // TODO: reuse last entry for matched keys and buckets? + b := buckets[i] + entry, has := r.entry(&r.buckets[b], key, curInt) + if !has { + // new entry, defaults are provided by entry() function + // admit and increment + entry.cur.Add(1) + continue + } + + rate := int64(float64(entry.prev.Load())*(1-curFraction)) + entry.cur.Load() + 1 + if rate > int64(r.serviceRatePerWindow) { + return true + } + entry.cur.Add(1) + } + + return false +} + +func (r *appRateLimiter) len() int { + var count int + for i := 0; i < numBuckets; i++ { + r.buckets[i].mu.RLock() + count += len(r.buckets[i].entries) + r.buckets[i].mu.RUnlock() + } + return count +} + +var appKeyPool = sync.Pool{ + New: func() interface{} { + return &appKeyBuf{ + // max config.MaxTxGroupSize apps per txgroup, each app has up to MaxAppTxnForeignApps extra foreign apps + // at moment of writing config.MaxTxGroupSize = 16, config.MaxAppTxnForeignApps = 8 + keys: make([]keyType, 0, config.MaxTxGroupSize*(1+config.MaxAppTxnForeignApps)), + buckets: make([]int, 0, config.MaxTxGroupSize*(1+config.MaxAppTxnForeignApps)), + } + }, +} + +// appKeyBuf is a reusable storage for key and bucket slices +type appKeyBuf struct { + keys []keyType + buckets []int +} + +func getAppKeyBuf() *appKeyBuf { + buf := appKeyPool.Get().(*appKeyBuf) + buf.buckets = buf.buckets[:0] + buf.keys = buf.keys[:0] + return buf +} + +func putAppKeyBuf(buf *appKeyBuf) { + appKeyPool.Put(buf) +} + +// txgroupToKeys converts txgroup data to keys +func txgroupToKeys(txgroup []transactions.SignedTxn, origin []byte, seed uint64, salt [16]byte, numBuckets int) *appKeyBuf { + keysBuckets := getAppKeyBuf() + // since blake2 is a crypto hash function it seems OK to shrink 32 bytes digest down to 8. + // Rationale: we expect thousands of apps sent from thousands of peers, + // so required millions of unique pairs => 8 bytes should be enough. + // The 16 bytes salt makes it harder to find collisions if an adversary attempts to censor + // some app by finding a collision with some app and flood a network with such transactions: + // h(app + relay_ip) = h(app2 + relay_ip). + + // uint64 + 16 bytes of salt + up to 16 bytes of address + // salt and origin are fixed so pre-copy them into the buf + var buf [8 + 16 + 16]byte + copy(buf[8:], salt[:]) + copied := copy(buf[8+16:], origin) + bufLen := 8 + 16 + copied + + txnToDigest := func(appIdx basics.AppIndex) (key keyType) { + binary.LittleEndian.PutUint64(buf[:8], uint64(appIdx)) + h := blake2b.Sum256(buf[:bufLen]) + copy(key[:], h[:len(keyType{})]) + return + } + txnToBucket := func(appIdx basics.AppIndex) int { + return int(memhash64(uint64(appIdx), seed) % uint64(numBuckets)) + } + seen := make(map[basics.AppIndex]struct{}, len(txgroup)*(1+config.MaxAppTxnForeignApps)) + valid := func(appIdx basics.AppIndex) bool { + if appIdx != 0 { + _, ok := seen[appIdx] + return !ok + } + return false + } + for i := range txgroup { + if txgroup[i].Txn.Type == protocol.ApplicationCallTx { + appIdx := txgroup[i].Txn.ApplicationID + if valid(appIdx) { + keysBuckets.buckets = append(keysBuckets.buckets, txnToBucket(appIdx)) + keysBuckets.keys = append(keysBuckets.keys, txnToDigest(appIdx)) + seen[appIdx] = struct{}{} + } + // hash appIdx into a bucket, do not use modulo without hashing first since it could + // assign two vanilla (and presumable, popular) apps to the same bucket. + if len(txgroup[i].Txn.ForeignApps) > 0 { + for _, appIdx := range txgroup[i].Txn.ForeignApps { + if valid(appIdx) { + keysBuckets.buckets = append(keysBuckets.buckets, txnToBucket(appIdx)) + keysBuckets.keys = append(keysBuckets.keys, txnToDigest(appIdx)) + seen[appIdx] = struct{}{} + } + } + } + } + } + return keysBuckets +} + +const ( + // Constants for multiplication: four random odd 64-bit numbers. + m1 = 16877499708836156737 + m2 = 2820277070424839065 + m3 = 9497967016996688599 + m4 = 15839092249703872147 +) + +// memhash64 is uint64 hash function from go runtime +// https://go-review.googlesource.com/c/go/+/59352/4/src/runtime/hash64.go#96 +func memhash64(val uint64, seed uint64) uint64 { + h := seed + h ^= val + h = rotl31(h*m1) * m2 + h ^= h >> 29 + h *= m3 + h ^= h >> 32 + return h +} + +func rotl31(x uint64) uint64 { + return (x << 31) | (x >> (64 - 31)) +} diff --git a/data/appRateLimiter_test.go b/data/appRateLimiter_test.go new file mode 100644 index 0000000000..2da9fddfa0 --- /dev/null +++ b/data/appRateLimiter_test.go @@ -0,0 +1,526 @@ +// 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 data + +import ( + "encoding/binary" + "testing" + "time" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "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/partitiontest" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/blake2b" + "golang.org/x/exp/rand" +) + +func TestAppRateLimiter_Make(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rate := uint64(10) + window := 1 * time.Second + rm := makeAppRateLimiter(10, rate, window) + + require.Equal(t, 1, rm.maxBucketSize) + require.NotEmpty(t, rm.seed) + require.NotEmpty(t, rm.salt) + for i := 0; i < len(rm.buckets); i++ { + require.NotNil(t, rm.buckets[i].entries) + require.NotNil(t, rm.buckets[i].lru) + } +} + +func TestAppRateLimiter_NoApps(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rate := uint64(10) + window := 1 * time.Second + rm := makeAppRateLimiter(10, rate, window) + + txns := []transactions.SignedTxn{ + { + Txn: transactions.Transaction{ + Type: protocol.AssetConfigTx, + }, + }, + { + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + }, + }, + } + drop := rm.shouldDrop(txns, nil) + require.False(t, drop) +} + +func getAppTxnGroup(appIdx basics.AppIndex) []transactions.SignedTxn { + apptxn := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: appIdx, + }, + } + + return []transactions.SignedTxn{{Txn: apptxn}} +} + +func TestAppRateLimiter_Basics(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rate := uint64(10) + window := 1 * time.Second + rm := makeAppRateLimiter(512, rate, window) + + txns := getAppTxnGroup(1) + now := time.Now().UnixNano() + drop := rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + + for i := len(txns); i < int(rate); i++ { + drop = rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + } + + drop = rm.shouldDropAt(txns, nil, now) + require.True(t, drop) + + require.Equal(t, 1, rm.len()) + + // check a single group cannot exceed the rate + apptxn2 := txns[0].Txn + apptxn2.ApplicationID = 2 + txns = make([]transactions.SignedTxn, 0, rate+1) + for i := 0; i < int(rate+1); i++ { + txns = append(txns, transactions.SignedTxn{ + Txn: apptxn2, + }) + } + drop = rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + + // check multple groups can exceed the rate (-1 comes from the previous check) + for i := 0; i < int(rate)-1; i++ { + drop = rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + } + drop = rm.shouldDropAt(txns, nil, now) + require.True(t, drop) + + require.Equal(t, 2, rm.len()) + + // check foreign apps in the same group do not trigger the rate limit + apptxn3 := txns[0].Txn + apptxn3.ApplicationID = 3 + for i := 0; i < int(rate); i++ { + apptxn3.ForeignApps = append(apptxn3.ForeignApps, 3) + } + txns = []transactions.SignedTxn{{Txn: apptxn3}} + drop = rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + + // check multple groups with foreign apps can exceed the rate (-1 comes from the previous check) + for i := 0; i < int(rate)-1; i++ { + drop = rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + } + drop = rm.shouldDropAt(txns, nil, now) + require.True(t, drop) + + require.Equal(t, 3, rm.len()) +} + +// TestAppRateLimiter_Interval checks prev + cur rate approximation logic +func TestAppRateLimiter_Interval(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rate := uint64(10) + window := 10 * time.Second + perSecondRate := uint64(window) / rate / uint64(time.Second) + rm := makeAppRateLimiter(512, perSecondRate, window) + + txns := getAppTxnGroup(1) + now := time.Date(2023, 9, 11, 10, 10, 11, 0, time.UTC).UnixNano() // 11 sec => 1 sec into the interval + + // fill 80% of the current interval + // switch to the next interval + // ensure only 30% of the rate is available (8 * 0.9 = 7.2 => 7) + // 0.9 is calculated as 1 - 0.1 (fraction of the interval elapsed) + // since the next interval at second 21 would by 1 sec (== 10% == 0.1) after the interval beginning + for i := 0; i < int(0.8*float64(rate)); i++ { + drop := rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + } + + next := now + int64(window) + for i := 0; i < int(0.3*float64(rate)); i++ { + drop := rm.shouldDropAt(txns, nil, next) + require.False(t, drop) + } + + drop := rm.shouldDropAt(txns, nil, next) + require.True(t, drop) +} + +// TestAppRateLimiter_IntervalFull checks the cur counter accounts only admitted requests +func TestAppRateLimiter_IntervalAdmitted(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rate := uint64(10) + window := 10 * time.Second + perSecondRate := uint64(window) / rate / uint64(time.Second) + rm := makeAppRateLimiter(512, perSecondRate, window) + + txns := getAppTxnGroup(1) + bk := txgroupToKeys(getAppTxnGroup(basics.AppIndex(1)), nil, rm.seed, rm.salt, numBuckets) + require.Equal(t, 1, len(bk.buckets)) + require.Equal(t, 1, len(bk.keys)) + b := bk.buckets[0] + k := bk.keys[0] + now := time.Date(2023, 9, 11, 10, 10, 11, 0, time.UTC).UnixNano() // 11 sec => 1 sec into the interval + + // fill a current interval with more than rate requests + // ensure the counter does not exceed the rate + for i := 0; i < int(rate); i++ { + drop := rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + } + drop := rm.shouldDropAt(txns, nil, now) + require.True(t, drop) + + entry := rm.buckets[b].entries[k] + require.NotNil(t, entry) + require.Equal(t, int64(rate), entry.cur.Load()) +} + +// TestAppRateLimiter_IntervalSkip checks that the rate is reset when no requests within some interval +func TestAppRateLimiter_IntervalSkip(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rate := uint64(10) + window := 10 * time.Second + perSecondRate := uint64(window) / rate / uint64(time.Second) + rm := makeAppRateLimiter(512, perSecondRate, window) + + txns := getAppTxnGroup(1) + now := time.Date(2023, 9, 11, 10, 10, 11, 0, time.UTC).UnixNano() // 11 sec => 1 sec into the interval + + // fill 80% of the current interval + // switch to the next next interval + // ensure all capacity is available + + for i := 0; i < int(0.8*float64(rate)); i++ { + drop := rm.shouldDropAt(txns, nil, now) + require.False(t, drop) + } + + nextnext := now + int64(2*window) + for i := 0; i < int(rate); i++ { + drop := rm.shouldDropAt(txns, nil, nextnext) + require.False(t, drop) + } + + drop := rm.shouldDropAt(txns, nil, nextnext) + require.True(t, drop) +} + +func TestAppRateLimiter_IPAddr(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rate := uint64(10) + window := 10 * time.Second + perSecondRate := uint64(window) / rate / uint64(time.Second) + rm := makeAppRateLimiter(512, perSecondRate, window) + + txns := getAppTxnGroup(1) + now := time.Now().UnixNano() + + for i := 0; i < int(rate); i++ { + drop := rm.shouldDropAt(txns, []byte{1}, now) + require.False(t, drop) + drop = rm.shouldDropAt(txns, []byte{2}, now) + require.False(t, drop) + } + + drop := rm.shouldDropAt(txns, []byte{1}, now) + require.True(t, drop) + drop = rm.shouldDropAt(txns, []byte{2}, now) + require.True(t, drop) +} + +// TestAppRateLimiter_MaxSize puts size+1 elements into a single bucket and ensures the total size is capped +func TestAppRateLimiter_MaxSize(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + const bucketSize = 4 + const size = bucketSize * numBuckets + const rate uint64 = 10 + window := 10 * time.Second + rm := makeAppRateLimiter(size, rate, window) + + for i := 1; i <= int(size)+1; i++ { + drop := rm.shouldDrop(getAppTxnGroup(basics.AppIndex(1)), []byte{byte(i)}) + require.False(t, drop) + } + bucket := int(memhash64(uint64(1), rm.seed) % numBuckets) + require.Equal(t, bucketSize, len(rm.buckets[bucket].entries)) + var totalSize int + for i := 0; i < len(rm.buckets); i++ { + totalSize += len(rm.buckets[i].entries) + if i != bucket { + require.Equal(t, 0, len(rm.buckets[i].entries)) + } + } + require.LessOrEqual(t, totalSize, int(size)) +} + +// TestAppRateLimiter_EvictOrder ensures that the least recent used is evicted +func TestAppRateLimiter_EvictOrder(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + const bucketSize = 4 + const size = bucketSize * numBuckets + const rate uint64 = 10 + window := 10 * time.Second + rm := makeAppRateLimiter(size, rate, window) + + keys := make([]keyType, 0, int(bucketSize)+1) + bucket := int(memhash64(uint64(1), rm.seed) % numBuckets) + for i := 0; i < bucketSize; i++ { + bk := txgroupToKeys(getAppTxnGroup(basics.AppIndex(1)), []byte{byte(i)}, rm.seed, rm.salt, numBuckets) + require.Equal(t, 1, len(bk.buckets)) + require.Equal(t, 1, len(bk.keys)) + require.Equal(t, bucket, bk.buckets[0]) + keys = append(keys, bk.keys[0]) + drop := rm.shouldDrop(getAppTxnGroup(basics.AppIndex(1)), []byte{byte(i)}) + require.False(t, drop) + } + require.Equal(t, bucketSize, len(rm.buckets[bucket].entries)) + + // add one more and expect the first evicted + bk := txgroupToKeys(getAppTxnGroup(basics.AppIndex(1)), []byte{byte(bucketSize)}, rm.seed, rm.salt, numBuckets) + require.Equal(t, 1, len(bk.buckets)) + require.Equal(t, 1, len(bk.keys)) + require.Equal(t, bucket, bk.buckets[0]) + drop := rm.shouldDrop(getAppTxnGroup(basics.AppIndex(1)), []byte{byte(bucketSize)}) + require.False(t, drop) + + require.Equal(t, bucketSize, len(rm.buckets[bucket].entries)) + require.NotContains(t, rm.buckets[bucket].entries, keys[0]) + for i := 1; i < len(keys); i++ { + require.Contains(t, rm.buckets[bucket].entries, keys[i]) + } + + var totalSize int + for i := 0; i < len(rm.buckets); i++ { + totalSize += len(rm.buckets[i].entries) + if i != bucket { + require.Equal(t, 0, len(rm.buckets[i].entries)) + } + } + require.LessOrEqual(t, totalSize, int(size)) +} + +func BenchmarkBlake2(b *testing.B) { + var salt [16]byte + crypto.RandBytes(salt[:]) + origin := make([]byte, 4) + + var buf [8 + 16 + 16]byte // uint64 + 16 bytes of salt + up to 16 bytes of address + + b.Run("blake2b-sum256", func(b *testing.B) { + total := 0 + for i := 0; i < b.N; i++ { + binary.LittleEndian.PutUint64(buf[:8], rand.Uint64()) + copy(buf[8:], salt[:]) + copied := copy(buf[8+16:], origin) + h := blake2b.Sum256(buf[:8+16+copied]) + total += len(h[:]) + } + b.Logf("total1: %d", total) // to prevent optimizing out the loop + }) + + b.Run("blake2b-sum8", func(b *testing.B) { + total := 0 + for i := 0; i < b.N; i++ { + d, err := blake2b.New(8, nil) + require.NoError(b, err) + + binary.LittleEndian.PutUint64(buf[:8], rand.Uint64()) + copy(buf[8:], salt[:]) + copied := copy(buf[8+16:], origin) + + _, err = d.Write(buf[:8+16+copied]) + require.NoError(b, err) + h := d.Sum([]byte{}) + total += len(h[:]) + } + b.Logf("total2: %d", total) + }) +} + +func BenchmarkAppRateLimiter(b *testing.B) { + cfg := config.GetDefaultLocal() + + b.Run("multi bucket no evict", func(b *testing.B) { + rm := makeAppRateLimiter( + cfg.TxBacklogAppTxRateLimiterMaxSize, + uint64(cfg.TxBacklogAppTxPerSecondRate), + time.Duration(cfg.TxBacklogServiceRateWindowSeconds)*time.Second, + ) + dropped := 0 + for i := 0; i < b.N; i++ { + if rm.shouldDrop(getAppTxnGroup(basics.AppIndex(i%512)), []byte{byte(i), byte(i % 256)}) { + dropped++ + } + } + b.ReportMetric(float64(dropped)/float64(b.N), "%_drop") + if rm.evictions > 0 { + b.Logf("# evictions %d, time %d us", rm.evictions, rm.evictionTime/uint64(time.Microsecond)) + } + }) + + b.Run("single bucket no evict", func(b *testing.B) { + rm := makeAppRateLimiter( + cfg.TxBacklogAppTxRateLimiterMaxSize, + uint64(cfg.TxBacklogAppTxPerSecondRate), + time.Duration(cfg.TxBacklogServiceRateWindowSeconds)*time.Second, + ) + dropped := 0 + for i := 0; i < b.N; i++ { + if rm.shouldDrop(getAppTxnGroup(basics.AppIndex(1)), []byte{byte(i), byte(i % 256)}) { + dropped++ + } + } + b.ReportMetric(float64(dropped)/float64(b.N), "%_drop") + if rm.evictions > 0 { + b.Logf("# evictions %d, time %d us", rm.evictions, rm.evictionTime/uint64(time.Microsecond)) + } + }) + + b.Run("single bucket w evict", func(b *testing.B) { + rm := makeAppRateLimiter( + cfg.TxBacklogAppTxRateLimiterMaxSize, + uint64(cfg.TxBacklogAppTxPerSecondRate), + time.Duration(cfg.TxBacklogServiceRateWindowSeconds)*time.Second, + ) + dropped := 0 + for i := 0; i < b.N; i++ { + if rm.shouldDrop(getAppTxnGroup(basics.AppIndex(1)), []byte{byte(i), byte(i / 256), byte(i % 256)}) { + dropped++ + } + } + b.ReportMetric(float64(dropped)/float64(b.N), "%_drop") + if rm.evictions > 0 { + b.Logf("# evictions %d, time %d us", rm.evictions, rm.evictionTime/uint64(time.Microsecond)) + } + }) +} + +func TestAppRateLimiter_TxgroupToKeys(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + apptxn := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 0, + ForeignApps: []basics.AppIndex{0}, + }, + } + txgroup := []transactions.SignedTxn{{Txn: apptxn}} + + kb := txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) + require.Equal(t, 0, len(kb.keys)) + require.Equal(t, len(kb.buckets), len(kb.buckets)) + putAppKeyBuf(kb) + + txgroup[0].Txn.ApplicationID = 1 + kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) + require.Equal(t, 1, len(kb.keys)) + require.Equal(t, len(kb.buckets), len(kb.buckets)) + putAppKeyBuf(kb) + + txgroup[0].Txn.ForeignApps = append(txgroup[0].Txn.ForeignApps, 1) + kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) + require.Equal(t, 1, len(kb.keys)) + require.Equal(t, len(kb.buckets), len(kb.buckets)) + putAppKeyBuf(kb) + + txgroup[0].Txn.ForeignApps = append(txgroup[0].Txn.ForeignApps, 2) + kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) + require.Equal(t, 2, len(kb.keys)) + require.Equal(t, len(kb.buckets), len(kb.buckets)) + putAppKeyBuf(kb) + + apptxn.ApplicationID = 2 + txgroup = append(txgroup, transactions.SignedTxn{Txn: apptxn}) + kb = txgroupToKeys(txgroup, nil, 123, [16]byte{}, 1) + require.Equal(t, 2, len(kb.keys)) + require.Equal(t, len(kb.buckets), len(kb.buckets)) + putAppKeyBuf(kb) +} + +func BenchmarkAppRateLimiter_TxgroupToKeys(b *testing.B) { + rnd := rand.New(rand.NewSource(123)) + + txgroups := make([][]transactions.SignedTxn, 0, b.N) + for i := 0; i < b.N; i++ { + txgroup := make([]transactions.SignedTxn, 0, config.MaxTxGroupSize) + for j := 0; j < config.MaxTxGroupSize; j++ { + apptxn := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: basics.AppIndex(rnd.Uint64()), + ForeignApps: []basics.AppIndex{basics.AppIndex(rnd.Uint64()), basics.AppIndex(rnd.Uint64()), basics.AppIndex(rnd.Uint64()), basics.AppIndex(rnd.Uint64())}, + }, + } + txgroup = append(txgroup, transactions.SignedTxn{Txn: apptxn}) + } + txgroups = append(txgroups, txgroup) + } + + b.ResetTimer() + b.ReportAllocs() + + origin := make([]byte, 4) + _, err := rnd.Read(origin) + require.NoError(b, err) + require.NotEmpty(b, origin) + + salt := [16]byte{} + _, err = rnd.Read(salt[:]) + require.NoError(b, err) + require.NotEmpty(b, salt) + + for i := 0; i < b.N; i++ { + kb := txgroupToKeys(txgroups[i], origin, 123, salt, numBuckets) + putAppKeyBuf(kb) + } +} diff --git a/data/txHandler.go b/data/txHandler.go index a5f44a1d07..4689a497b4 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -56,6 +56,7 @@ var transactionMessageTxGroupExcessive = metrics.MakeCounter(metrics.Transaction var transactionMessageTxGroupFull = metrics.MakeCounter(metrics.TransactionMessageTxGroupFull) var transactionMessagesDupRawMsg = metrics.MakeCounter(metrics.TransactionMessagesDupRawMsg) var transactionMessagesDupCanonical = metrics.MakeCounter(metrics.TransactionMessagesDupCanonical) +var transactionMessagesAppLimiterDrop = metrics.MakeCounter(metrics.TransactionMessagesAppLimiterDrop) var transactionMessagesBacklogSizeGauge = metrics.MakeGauge(metrics.TransactionMessagesBacklogSize) var transactionGroupTxSyncHandled = metrics.MakeCounter(metrics.TransactionGroupTxSyncHandled) @@ -111,23 +112,25 @@ type txBacklogMsg struct { // TxHandler handles transaction messages type TxHandler struct { - txPool *pools.TransactionPool - ledger *Ledger - genesisID string - genesisHash crypto.Digest - txVerificationPool execpool.BacklogPool - backlogQueue chan *txBacklogMsg - postVerificationQueue chan *verify.VerificationResult - backlogWg sync.WaitGroup - net network.GossipNode - msgCache *txSaltedCache - txCanonicalCache *digestCache - ctx context.Context - ctxCancel context.CancelFunc - streamVerifier *execpool.StreamToBatch - streamVerifierChan chan execpool.InputJob - streamVerifierDropped chan *verify.UnverifiedTxnSigJob - erl *util.ElasticRateLimiter + txPool *pools.TransactionPool + ledger *Ledger + genesisID string + genesisHash crypto.Digest + txVerificationPool execpool.BacklogPool + backlogQueue chan *txBacklogMsg + backlogCongestionThreshold float64 + postVerificationQueue chan *verify.VerificationResult + backlogWg sync.WaitGroup + net network.GossipNode + msgCache *txSaltedCache + txCanonicalCache *digestCache + ctx context.Context + ctxCancel context.CancelFunc + streamVerifier *execpool.StreamToBatch + streamVerifierChan chan execpool.InputJob + streamVerifierDropped chan *verify.UnverifiedTxnSigJob + erl *util.ElasticRateLimiter + appLimiter *appRateLimiter } // TxHandlerOpts is TxHandler configuration options @@ -178,14 +181,29 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { handler.txCanonicalCache = makeDigestCache(int(opts.Config.TxIncomingFilterMaxSize)) } - if opts.Config.EnableTxBacklogRateLimiting { - rateLimiter := util.NewElasticRateLimiter( - txBacklogSize, - opts.Config.TxBacklogReservedCapacityPerPeer, - time.Duration(opts.Config.TxBacklogServiceRateWindowSeconds)*time.Second, - txBacklogDroppedCongestionManagement, - ) - handler.erl = rateLimiter + if opts.Config.EnableTxBacklogRateLimiting || opts.Config.EnableTxBacklogAppRateLimiting { + if opts.Config.TxBacklogRateLimitingCongestionPct > 100 || opts.Config.TxBacklogRateLimitingCongestionPct < 0 { + return nil, fmt.Errorf("invalid value for TxBacklogRateLimitingCongestionPct: %d", opts.Config.TxBacklogRateLimitingCongestionPct) + } + if opts.Config.EnableTxBacklogAppRateLimiting && opts.Config.TxBacklogAppTxRateLimiterMaxSize == 0 { + return nil, fmt.Errorf("invalid value for TxBacklogAppTxRateLimiterMaxSize: %d. App rate limiter enabled with zero size", opts.Config.TxBacklogAppTxRateLimiterMaxSize) + } + handler.backlogCongestionThreshold = float64(opts.Config.TxBacklogRateLimitingCongestionPct) / 100 + if opts.Config.EnableTxBacklogRateLimiting { + handler.erl = util.NewElasticRateLimiter( + txBacklogSize, + opts.Config.TxBacklogReservedCapacityPerPeer, + time.Duration(opts.Config.TxBacklogServiceRateWindowSeconds)*time.Second, + txBacklogDroppedCongestionManagement, + ) + } + if opts.Config.EnableTxBacklogAppRateLimiting { + handler.appLimiter = makeAppRateLimiter( + opts.Config.TxBacklogAppTxRateLimiterMaxSize, + uint64(opts.Config.TxBacklogAppTxPerSecondRate), + time.Duration(opts.Config.TxBacklogServiceRateWindowSeconds)*time.Second, + ) + } } // prepare the transaction stream verifier @@ -578,7 +596,9 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net var err error var capguard *util.ErlCapacityGuard + var congested bool if handler.erl != nil { + congested = float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue)) // consume a capacity unit // if the elastic rate limiter cannot vend a capacity, the error it returns // is sufficient to indicate that we should enable Congestion Control, because @@ -591,7 +611,7 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net return network.OutgoingMessage{Action: network.Ignore} } // if the backlog Queue has 50% of its buffer back, turn congestion control off - if float64(cap(handler.backlogQueue))*0.5 > float64(len(handler.backlogQueue)) { + if !congested { handler.erl.DisableCongestionControl() } } @@ -640,6 +660,12 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net } } + // rate limit per application in a group. Limiting any app in a group drops the entire message. + if handler.appLimiter != nil && congested && handler.appLimiter.shouldDrop(unverifiedTxGroup, rawmsg.Sender.(network.IPAddressable).RoutingAddr()) { + transactionMessagesAppLimiterDrop.Inc(nil) + return network.OutgoingMessage{Action: network.Ignore} + } + select { case handler.backlogQueue <- &txBacklogMsg{ rawmsg: &rawmsg, diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 486bd7c0f8..894fef9d4d 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -62,6 +62,9 @@ type mockSender struct{} func (m mockSender) OnClose(func()) {} +func (m mockSender) IPAddr() []byte { return nil } +func (m mockSender) RoutingAddr() []byte { return nil } + // txHandlerConfig is a subset of tx handler related options from config.Local type txHandlerConfig struct { enableFilteringRawMsg bool @@ -2503,3 +2506,178 @@ func TestTxHandlerRestartWithBacklogAndTxPool(t *testing.T) { //nolint:parallelt require.False(t, inBad, "invalid transaction accepted") } } + +// check ERL and AppRateLimiter enablement with separate config values, +// and the app limiter kicks in after congestion. +func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ledgerName := fmt.Sprintf("%s-mem", t.Name()) + const inMem = true + log := logging.TestingLog(t) + log.SetLevel(logging.Panic) + + cfg := config.GetDefaultLocal() + cfg.TxBacklogAppTxRateLimiterMaxSize = 100 + cfg.TxBacklogServiceRateWindowSeconds = 1 + cfg.TxBacklogAppTxPerSecondRate = 3 + cfg.TxBacklogReservedCapacityPerPeer = 2 + cfg.TxBacklogSize = 1 + cfg.IncomingConnectionsLimit = 1 + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, bookkeeping.GenesisBalances{}, genesisID, genesisHash, nil, cfg) + require.NoError(t, err) + defer ledger.Close() + + l := ledger + + func() { + cfg.EnableTxBacklogRateLimiting = false + cfg.EnableTxBacklogAppRateLimiting = false + handler, err := makeTestTxHandler(l, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + + require.Nil(t, handler.erl) + require.Nil(t, handler.appLimiter) + }() + + func() { + cfg.EnableTxBacklogRateLimiting = true + cfg.EnableTxBacklogAppRateLimiting = false + handler, err := makeTestTxHandler(l, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + + require.NotNil(t, handler.erl) + require.Nil(t, handler.appLimiter) + }() + + cfg.EnableTxBacklogRateLimiting = true + cfg.EnableTxBacklogAppRateLimiting = true + handler, err := makeTestTxHandler(l, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + require.NotNil(t, handler.erl) + require.NotNil(t, handler.appLimiter) + + var addr basics.Address + crypto.RandBytes(addr[:]) + + tx := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: transactions.Header{ + Sender: addr, + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: 0, + LastValid: basics.Round(proto.MaxTxnLife), + Note: make([]byte, 2), + }, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 1, + }, + } + signedTx := tx.Sign(keypair()) // some random key + blob := protocol.Encode(&signedTx) + sender := mockSender{} + + // submit and ensure it is accepted + congested := float64(cap(handler.backlogQueue))*0.5 < float64(len(handler.backlogQueue)) + require.False(t, congested) + + action := handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender}) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) + require.Equal(t, 1, len(handler.backlogQueue)) + + // repeat the same txn, we are still not congested + congested = float64(cap(handler.backlogQueue))*0.5 < float64(len(handler.backlogQueue)) + require.False(t, congested) + + signedTx = tx.Sign(keypair()) + blob = protocol.Encode(&signedTx) + action = handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender}) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) + require.Equal(t, 2, len(handler.backlogQueue)) + require.Equal(t, 0, handler.appLimiter.len()) // no rate limiting yet + + congested = float64(cap(handler.backlogQueue))*0.5 < float64(len(handler.backlogQueue)) + require.True(t, congested) + + // submit it again and the app rate limiter should kick in + signedTx = tx.Sign(keypair()) + blob = protocol.Encode(&signedTx) + action = handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender}) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) + require.Equal(t, 3, len(handler.backlogQueue)) + + require.Equal(t, 1, handler.appLimiter.len()) +} + +func TestTxHandlerAppRateLimiter(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + const numUsers = 10 + log := logging.TestingLog(t) + log.SetLevel(logging.Panic) + + // prepare the accounts + addresses, secrets, genesis := makeTestGenesisAccounts(t, numUsers) + genBal := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr) + ledgerName := fmt.Sprintf("%s-mem", t.Name()) + const inMem = true + + cfg := config.GetDefaultLocal() + cfg.EnableTxBacklogRateLimiting = true + cfg.TxBacklogAppTxRateLimiterMaxSize = 100 + cfg.TxBacklogServiceRateWindowSeconds = 1 + cfg.TxBacklogAppTxPerSecondRate = 3 + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + require.NoError(t, err) + defer ledger.Close() + + l := ledger + handler, err := makeTestTxHandler(l, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + + tx := transactions.Transaction{ + Type: protocol.ApplicationCallTx, + Header: transactions.Header{ + Sender: addresses[0], + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: 0, + LastValid: basics.Round(proto.MaxTxnLife), + Note: make([]byte, 2), + }, + ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ + ApplicationID: 1, + }, + } + signedTx := tx.Sign(secrets[1]) + blob := protocol.Encode(&signedTx) + + action := handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: mockSender{}}) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) + require.Equal(t, 1, len(handler.backlogQueue)) + + // trigger the rate limiter and ensure the txn is ignored + tx2 := tx + for i := 0; i < cfg.TxBacklogAppTxPerSecondRate*cfg.TxBacklogServiceRateWindowSeconds; i++ { + tx2.ForeignApps = append(tx2.ForeignApps, 1) + } + signedTx2 := tx.Sign(secrets[1]) + blob2 := protocol.Encode(&signedTx2) + + action = handler.processIncomingTxn(network.IncomingMessage{Data: blob2, Sender: mockSender{}}) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) + require.Equal(t, 1, len(handler.backlogQueue)) + + // backlogQueue has the first txn, but the second one is dropped + msg := <-handler.backlogQueue + require.Equal(t, msg.rawmsg.Data, blob, blob) +} diff --git a/installer/config.json.example b/installer/config.json.example index fccf558c44..ce02380331 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -1,5 +1,5 @@ { - "Version": 31, + "Version": 32, "AccountUpdatesStatsInterval": 5000000000, "AccountsRebuildSynchronousMode": 1, "AgreementIncomingBundlesQueueLength": 15, @@ -60,6 +60,7 @@ "EnableRequestLogger": false, "EnableRuntimeMetrics": false, "EnableTopAccountsReporting": false, + "EnableTxBacklogAppRateLimiting": true, "EnableTxBacklogRateLimiting": true, "EnableTxnEvalTracer": false, "EnableUsageLog": false, @@ -120,6 +121,9 @@ "TrackerDBDir": "", "TransactionSyncDataExchangeRate": 0, "TransactionSyncSignificantMessageThreshold": 0, + "TxBacklogAppTxPerSecondRate": 100, + "TxBacklogAppTxRateLimiterMaxSize": 1048576, + "TxBacklogRateLimitingCongestionPct": 50, "TxBacklogReservedCapacityPerPeer": 20, "TxBacklogServiceRateWindowSeconds": 10, "TxBacklogSize": 26000, diff --git a/network/p2pPeer.go b/network/p2pPeer.go index 7d788180e6..343459d243 100644 --- a/network/p2pPeer.go +++ b/network/p2pPeer.go @@ -23,10 +23,12 @@ import ( "net" "time" + "github.com/algorand/go-algorand/logging" "github.com/algorand/websocket" "github.com/libp2p/go-libp2p/core/network" yamux "github.com/libp2p/go-yamux/v4" + mnet "github.com/multiformats/go-multiaddr/net" ) type wsPeerConnP2PImpl struct { @@ -82,3 +84,11 @@ func (c *wsPeerConnP2PImpl) CloseWithoutFlush() error { } func (c *wsPeerConnP2PImpl) UnderlyingConn() net.Conn { return nil } + +func (c *wsPeerConnP2PImpl) RemoteAddr() net.Addr { + netaddr, err := mnet.ToNetAddr(c.stream.Conn().RemoteMultiaddr()) + if err != nil { + logging.Base().Errorf("Error converting multiaddr to netaddr: %v", err) + } + return netaddr +} diff --git a/network/wsPeer.go b/network/wsPeer.go index 9daf7b0ece..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 @@ -321,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 { @@ -369,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 { diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go index b6f3a4d2f0..59217047ce 100644 --- a/network/wsPeer_test.go +++ b/network/wsPeer_test.go @@ -22,6 +22,8 @@ import ( "go/ast" "go/parser" "go/token" + "io" + "net" "path/filepath" "sort" "strings" @@ -264,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/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/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"} From 22b709678dbe8e3a52449dd187c708f7f1290556 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 9 Nov 2023 15:33:22 -0500 Subject: [PATCH 009/218] catchup: Dynamic parallel catchup (#5802) --- catchup/service.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/catchup/service.go b/catchup/service.go index 982747ba3c..d755a3d559 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -41,6 +41,11 @@ import ( const catchupPeersForSync = 10 const blockQueryPeerLimit = 10 +// uncapParallelDownloadRate is a simple threshold to detect whether or not the node is caught up. +// If a block is downloaded in less than this duration, it's assumed that the node is not caught up +// and allow the block downloader to start N=parallelBlocks concurrent fetches. +const uncapParallelDownloadRate = time.Second + // this should be at least the number of relays const catchupRetryLimit = 500 @@ -85,6 +90,7 @@ type Service struct { auth BlockAuthenticator parallelBlocks uint64 deadlineTimeout time.Duration + prevBlockFetchTime time.Time blockValidationPool execpool.BacklogPool // suspendForLedgerOps defines whether we've run into a state where the ledger is currently busy writing the @@ -448,9 +454,16 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo // TODO the following code does not handle the following case: seedLookback upgrades during fetch func (s *Service) pipelinedFetch(seedLookback uint64) { - parallelRequests := s.parallelBlocks - if parallelRequests < seedLookback { - parallelRequests = seedLookback + maxParallelRequests := s.parallelBlocks + if maxParallelRequests < seedLookback { + maxParallelRequests = seedLookback + } + minParallelRequests := seedLookback + + // Start the limited requests at max(1, 'seedLookback') + limitedParallelRequests := uint64(1) + if limitedParallelRequests < seedLookback { + limitedParallelRequests = seedLookback } completed := make(map[basics.Round]chan bool) @@ -480,7 +493,8 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { nextRound := firstRound for { - for nextRound < firstRound+basics.Round(parallelRequests) { + // launch N=parallelRequests block download go routines. + for nextRound < firstRound+basics.Round(limitedParallelRequests) { if s.roundIsNotSupported(nextRound) { // Break out of the loop to avoid fetching // blocks that we don't support. If there @@ -504,6 +518,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { nextRound++ } + // wait for the first round to complete before starting the next download. select { case completedOK := <-completed[firstRound]: delete(completed, firstRound) @@ -514,6 +529,15 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { return } + fetchTime := time.Now() + fetchDur := fetchTime.Sub(s.prevBlockFetchTime) + s.prevBlockFetchTime = fetchTime + if fetchDur < uncapParallelDownloadRate { + limitedParallelRequests = maxParallelRequests + } else { + limitedParallelRequests = minParallelRequests + } + // if ledger is busy, pause for some time to let the fetchAndWrite goroutines to finish fetching in-flight blocks. start := time.Now() for (s.ledger.IsWritingCatchpointDataFile() || s.ledger.IsBehindCommittingDeltas()) && time.Since(start) < s.deadlineTimeout { From 1bb78de72f9e890d576c0203a3045b7febe0b8ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 16:32:53 -0500 Subject: [PATCH 010/218] Build: bump github.com/consensys/gnark-crypto from 0.12.0 to 0.12.1 (#5822) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ohill <145173879+ohill@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- tools/block-generator/go.mod | 2 +- tools/block-generator/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 87faed7d03..8844a80585 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/algorand/websocket v1.4.6 github.com/aws/aws-sdk-go v1.34.0 github.com/cockroachdb/pebble v0.0.0-20230807162746-af8c5f279001 - github.com/consensys/gnark-crypto v0.12.0 + github.com/consensys/gnark-crypto v0.12.1 github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c github.com/dchest/siphash v1.2.1 github.com/fatih/color v1.13.0 diff --git a/go.sum b/go.sum index 91453673df..1d4e5abf63 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6/go.mod h1: github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= 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.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg= -github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +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= diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 79a485c24f..965cbd9a09 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -35,7 +35,7 @@ require ( github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 // indirect github.com/consensys/bavard v0.1.13 // indirect - github.com/consensys/gnark-crypto v0.12.0 // 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 diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index d4620db58c..dbba8cc727 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -119,8 +119,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6/go.mod h1: github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= 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.0 h1:1OnSpOykNkUIBIBJKdhwy2p0JlW5o+Az02ICzZmvvdg= -github.com/consensys/gnark-crypto v0.12.0/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +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= From 3d3440c3f83ac30eeb7601e15af4972d080c4433 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Thu, 9 Nov 2023 21:38:03 +0000 Subject: [PATCH 011/218] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +0 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885 From 90b10d221d73161cae009e1d30ee389c3b544c90 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Mon, 13 Nov 2023 14:34:55 -0500 Subject: [PATCH 012/218] algocfg: Add print option to algocfg. (#5824) --- cmd/algocfg/profileCommand.go | 20 +++++- cmd/algocfg/resetCommand.go | 2 +- cmd/algocfg/setCommand.go | 2 +- config/localTemplate.go | 2 +- util/codecs/json.go | 67 +++++++++---------- util/codecs/json_test.go | 121 +++++++++++++++++++++++++++++++++- 6 files changed, 175 insertions(+), 39 deletions(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index e629f2578a..0a368fc975 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -92,6 +92,7 @@ func init() { rootCmd.AddCommand(profileCmd) profileCmd.AddCommand(setProfileCmd) setProfileCmd.Flags().BoolVarP(&forceUpdate, "yes", "y", false, "Force updates to be written") + profileCmd.AddCommand(printProfileCmd) profileCmd.AddCommand(listProfileCmd) } @@ -133,6 +134,23 @@ var listProfileCmd = &cobra.Command{ }, } +var printProfileCmd = &cobra.Command{ + Use: "print", + Short: "Print config.json to stdout.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + cfg, err := getConfigForArg(args[0]) + if err != nil { + reportErrorf("%v", err) + } + err = codecs.WriteNonDefaultValues(os.Stdout, cfg, config.GetDefaultLocal(), nil) + if err != nil { + reportErrorf("Error writing config file to stdout: %s", err) + } + fmt.Fprintf(os.Stdout, "\n") + }, +} + var setProfileCmd = &cobra.Command{ Use: "set", Short: "Set config.json file from a profile.", @@ -157,7 +175,7 @@ var setProfileCmd = &cobra.Command{ return } } - err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil, true) + err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil) if err != nil { reportErrorf("Error saving updated config file '%s' - %s", file, err) } diff --git a/cmd/algocfg/resetCommand.go b/cmd/algocfg/resetCommand.go index 24f9cf1dad..2ec8c55aad 100644 --- a/cmd/algocfg/resetCommand.go +++ b/cmd/algocfg/resetCommand.go @@ -63,7 +63,7 @@ var resetCmd = &cobra.Command{ } file := filepath.Join(dataDir, config.ConfigFilename) - err = codecs.SaveNonDefaultValuesToFile(file, cfg, defaults, nil, true) + err = codecs.SaveNonDefaultValuesToFile(file, cfg, defaults, nil) if err != nil { reportWarnf("Error saving updated config file '%s' - %s", file, err) anyError = true diff --git a/cmd/algocfg/setCommand.go b/cmd/algocfg/setCommand.go index 8367857592..58f7ee796d 100644 --- a/cmd/algocfg/setCommand.go +++ b/cmd/algocfg/setCommand.go @@ -66,7 +66,7 @@ var setCmd = &cobra.Command{ } file := filepath.Join(dataDir, config.ConfigFilename) - err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil, true) + err = codecs.SaveNonDefaultValuesToFile(file, cfg, config.GetDefaultLocal(), nil) if err != nil { reportWarnf("Error saving updated config file '%s' - %s", file, err) anyError = true diff --git a/config/localTemplate.go b/config/localTemplate.go index 7e92d5ed7a..6748801209 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -666,7 +666,7 @@ func (cfg Local) SaveAllToDisk(root string) error { func (cfg Local) SaveToFile(filename string) error { var alwaysInclude []string alwaysInclude = append(alwaysInclude, "Version") - return codecs.SaveNonDefaultValuesToFile(filename, cfg, defaultLocal, alwaysInclude, true) + return codecs.SaveNonDefaultValuesToFile(filename, cfg, defaultLocal, alwaysInclude) } // DNSSecuritySRVEnforced returns true if SRV response verification enforced 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()) + }) + } +} From 243223fba17074244b5751c224997b10ae04b035 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Thu, 16 Nov 2023 00:33:58 +0100 Subject: [PATCH 013/218] typo: fix typos in md files (#5834) --- README.md | 2 +- data/transactions/logic/README.md | 8 ++++---- data/transactions/logic/README_in.md | 8 ++++---- data/transactions/logic/jsonspec.md | 2 +- docs/follower_node.md | 4 ++-- scripts/windows/instructions.md | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 682d884367..f1ebceceaa 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Please refer to our [CONTRIBUTING](CONTRIBUTING.md) document. ## Project Layout -`go-algorand` is split into various subsystems containing varius packages. +`go-algorand` is split into various subsystems containing various packages. ### Core diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 1ec45b6e2c..3ba9037fc1 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -341,7 +341,7 @@ An application transaction must indicate the action to be taken following the ex Most operations work with only one type of argument, uint64 or bytes, and fail if the wrong type value is on the stack. -Many instructions accept values to designate Accounts, Assets, or Applications. Beginning with v4, these values may be given as an _offset_ in the corresponding Txn fields (Txn.Accounts, Txn.ForeignAssets, Txn.ForeignApps) _or_ as the value itself (a byte-array address for Accounts, or a uint64 ID). The values, however, must still be present in the Txn fields. Before v4, most opcodes required the use of an offset, except for reading account local values of assets or applications, which accepted the IDs directly and did not require the ID to be present in they corresponding _Foreign_ array. (Note that beginning with v4, those IDs _are_ required to be present in their corresponding _Foreign_ array.) See individual opcodes for details. In the case of account offsets or application offsets, 0 is specially defined to Txn.Sender or the ID of the current application, respectively. +Many instructions accept values to designate Accounts, Assets, or Applications. Beginning with v4, these values may be given as an _offset_ in the corresponding Txn fields (Txn.Accounts, Txn.ForeignAssets, Txn.ForeignApps) _or_ as the value itself (a byte-array address for Accounts, or a uint64 ID). The values, however, must still be present in the Txn fields. Before v4, most opcodes required the use of an offset, except for reading account local values of assets or applications, which accepted the IDs directly and did not require the ID to be present in the corresponding _Foreign_ array. (Note that beginning with v4, those IDs _are_ required to be present in their corresponding _Foreign_ array.) See individual opcodes for details. In the case of account offsets or application offsets, 0 is specially defined to Txn.Sender or the ID of the current application, respectively. This summary is supplemented by more detail in the [opcodes document](TEAL_opcodes.md). @@ -775,7 +775,7 @@ are sure to be _available_. The following opcodes allow for "inner transactions". Inner transactions allow stateful applications to have many of the effects -of a true top-level transaction, programatically. However, they are +of a true top-level transaction, programmatically. However, they are different in significant ways. The most important differences are that they are not signed, duplicates are not rejected, and they do not appear in the block in the usual away. Instead, their effects are @@ -786,7 +786,7 @@ account that has been rekeyed to that hash. In v5, inner transactions may perform `pay`, `axfer`, `acfg`, and `afrz` effects. After executing an inner transaction with -`itxn_submit`, the effects of the transaction are visible begining +`itxn_submit`, the effects of the transaction are visible beginning with the next instruction with, for example, `balance` and `min_balance` checks. In v6, inner transactions may also perform `keyreg` and `appl` effects. Inner `appl` calls fail if they attempt @@ -806,7 +806,7 @@ setting is used when `itxn_submit` executes. For this purpose `Type` and `TypeEnum` are considered to be the same field. When using `itxn_field` to set an array field (`ApplicationArgs` `Accounts`, `Assets`, or `Applications`) each use adds an element to the end of -the the array, rather than setting the entire array at once. +the array, rather than setting the entire array at once. `itxn_field` fails immediately for unsupported fields, unsupported transaction types, or improperly typed values for a particular diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index ecbf2c6bbb..e98d6c2441 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -300,7 +300,7 @@ of (varuint, bytes) length prefixed byte strings. Most operations work with only one type of argument, uint64 or bytes, and fail if the wrong type value is on the stack. -Many instructions accept values to designate Accounts, Assets, or Applications. Beginning with v4, these values may be given as an _offset_ in the corresponding Txn fields (Txn.Accounts, Txn.ForeignAssets, Txn.ForeignApps) _or_ as the value itself (a byte-array address for Accounts, or a uint64 ID). The values, however, must still be present in the Txn fields. Before v4, most opcodes required the use of an offset, except for reading account local values of assets or applications, which accepted the IDs directly and did not require the ID to be present in they corresponding _Foreign_ array. (Note that beginning with v4, those IDs _are_ required to be present in their corresponding _Foreign_ array.) See individual opcodes for details. In the case of account offsets or application offsets, 0 is specially defined to Txn.Sender or the ID of the current application, respectively. +Many instructions accept values to designate Accounts, Assets, or Applications. Beginning with v4, these values may be given as an _offset_ in the corresponding Txn fields (Txn.Accounts, Txn.ForeignAssets, Txn.ForeignApps) _or_ as the value itself (a byte-array address for Accounts, or a uint64 ID). The values, however, must still be present in the Txn fields. Before v4, most opcodes required the use of an offset, except for reading account local values of assets or applications, which accepted the IDs directly and did not require the ID to be present in the corresponding _Foreign_ array. (Note that beginning with v4, those IDs _are_ required to be present in their corresponding _Foreign_ array.) See individual opcodes for details. In the case of account offsets or application offsets, 0 is specially defined to Txn.Sender or the ID of the current application, respectively. This summary is supplemented by more detail in the [opcodes document](TEAL_opcodes.md). @@ -422,7 +422,7 @@ are sure to be _available_. The following opcodes allow for "inner transactions". Inner transactions allow stateful applications to have many of the effects -of a true top-level transaction, programatically. However, they are +of a true top-level transaction, programmatically. However, they are different in significant ways. The most important differences are that they are not signed, duplicates are not rejected, and they do not appear in the block in the usual away. Instead, their effects are @@ -433,7 +433,7 @@ account that has been rekeyed to that hash. In v5, inner transactions may perform `pay`, `axfer`, `acfg`, and `afrz` effects. After executing an inner transaction with -`itxn_submit`, the effects of the transaction are visible begining +`itxn_submit`, the effects of the transaction are visible beginning with the next instruction with, for example, `balance` and `min_balance` checks. In v6, inner transactions may also perform `keyreg` and `appl` effects. Inner `appl` calls fail if they attempt @@ -453,7 +453,7 @@ setting is used when `itxn_submit` executes. For this purpose `Type` and `TypeEnum` are considered to be the same field. When using `itxn_field` to set an array field (`ApplicationArgs` `Accounts`, `Assets`, or `Applications`) each use adds an element to the end of -the the array, rather than setting the entire array at once. +the array, rather than setting the entire array at once. `itxn_field` fails immediately for unsupported fields, unsupported transaction types, or improperly typed values for a particular diff --git a/data/transactions/logic/jsonspec.md b/data/transactions/logic/jsonspec.md index 817c01ece4..0df95373fa 100644 --- a/data/transactions/logic/jsonspec.md +++ b/data/transactions/logic/jsonspec.md @@ -50,7 +50,7 @@ Duplicate keys at the top level result in an error; however, duplicate keys nest #### Special Values - `null`, `true`, `false` are the only accepted special values. -- other spcial values such as `NaN`,`+Inf`,`-Inf` are not accepted +- other special values such as `NaN`,`+Inf`,`-Inf` are not accepted #### Exponential Notation diff --git a/docs/follower_node.md b/docs/follower_node.md index 8df2306417..742d4efe44 100644 --- a/docs/follower_node.md +++ b/docs/follower_node.md @@ -34,7 +34,7 @@ Behavior is controlled with the `config.json` file: On startup, a follower node will be paused (synchronized) with its ledger's current round. For a new deployment configured as a follower node, the -initial sync round is 0. When a sync round is set, the node advance +initial sync round is 0. When a sync round is set, the node advances `MaxAcctLookback-1` rounds. The node is synchronized for the availability of `Ledger State Delta` data. This means the minimum sync round is provided and the node advances to cache future rounds. @@ -56,7 +56,7 @@ The follower node was stripped of all functionality not directly related to assisting with data-gathering capabilities. Since it is designed to run alongside another application, it was made as lightweight as possible. Other restrictions relate to the fact that this node is designed to be -paused. So there are no guarantees that it's internal state matches the +paused. So there are no guarantees that its internal state matches the current round of consensus. In particular, the follower node cannot participate in consensus or send diff --git a/scripts/windows/instructions.md b/scripts/windows/instructions.md index b24370bcf7..6b388d1d1e 100644 --- a/scripts/windows/instructions.md +++ b/scripts/windows/instructions.md @@ -8,7 +8,7 @@ pacman -Syu --disable-download-timeout ``` - NOTE: It is very likely MSYS2 will ask to close the window and repeat the command for furter updates. Check `MSYS2` web page for additional support. + NOTE: It is very likely MSYS2 will ask to close the window and repeat the command for further updates. Check `MSYS2` web page for additional support. 4. Install GIT on MSYS2 by executing the following command: From 5d047a46f4519dbf7f342139e659df91cffc976d Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 15 Nov 2023 21:07:57 -0500 Subject: [PATCH 014/218] ledger: rollback vetting historical stateproof blocks (#5830) --- ledger/eval/eval.go | 43 +++++--- ledger/eval/eval_test.go | 231 +++++++++------------------------------ 2 files changed, 75 insertions(+), 199 deletions(-) diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 66b1133457..58f7fc8a4f 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -1318,23 +1318,30 @@ func (eval *BlockEvaluator) endOfBlock() error { if !eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment.IsEqual(expectedVoters) { return fmt.Errorf("StateProofVotersCommitment wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, expectedVoters) } - - if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { - mainnetGenesisHash, _ := crypto.DigestFromString("YBQ4JWH4DW655UWXMBF6IVUOH5WQIGMHVQ333ZFWEC22WOJERLPQ") - if eval.genesisHash == mainnetGenesisHash { - // Handful of historic rounds where the state proof online total weight is known to be slightly different - // than expected voters weight. A consensus release (V38) addressed this/prevented the corner case from - // occurring going forward. - switch eval.block.Round() { - case 24018688, 26982912, 27009024, 27713280, 27822080, 27822848, 27929344, 28032768, - 28977920, 29822208, 30005248, 30033920: - // Don't return an error for these blocks - default: - return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, expectedVotersWeight) - } - } else { + if eval.proto.ExcludeExpiredCirculation { + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, expectedVotersWeight) } + } else { + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { + actualVotersWeight := eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight + var highWeight, lowWeight basics.MicroAlgos + if expectedVotersWeight.LessThan(actualVotersWeight) { + highWeight = actualVotersWeight + lowWeight = expectedVotersWeight + } else { + highWeight = expectedVotersWeight + lowWeight = actualVotersWeight + } + const stakeDiffusionFactor = 1 + allowedDelta, overflowed := basics.Muldiv(expectedVotersWeight.Raw, stakeDiffusionFactor, 100) + if overflowed { + return fmt.Errorf("StateProofOnlineTotalWeight overflow: %v != %v", actualVotersWeight, expectedVotersWeight) + } + if (highWeight.Raw - lowWeight.Raw) > allowedDelta { + return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v greater than %d", actualVotersWeight, expectedVotersWeight, allowedDelta) + } + } } if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound != eval.state.GetStateProofNextRound() { return fmt.Errorf("StateProofNextRound wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound, eval.state.GetStateProofNextRound()) @@ -1708,12 +1715,12 @@ transactionGroupLoop: select { case <-ctx.Done(): return ledgercore.StateDelta{}, ctx.Err() - case err1, open := <-txvalidator.done: + case err, open := <-txvalidator.done: if !open { break } - if err1 != nil { - return ledgercore.StateDelta{}, err1 + if err != nil { + return ledgercore.StateDelta{}, err } } } diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 94203b4bbb..b916558314 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -21,8 +21,6 @@ import ( "context" "errors" "fmt" - "github.com/algorand/go-algorand/crypto/merklearray" - "math" "math/rand" "testing" @@ -673,6 +671,56 @@ func testnetFixupExecution(t *testing.T, headerRound basics.Round, poolBonus uin require.NoError(t, err) } +// newTestGenesis creates a bunch of accounts, splits up 10B algos +// between them and the rewardspool and feesink, and gives out the +// addresses and secrets it creates to enable tests. For special +// scenarios, manipulate these return values before using newTestLedger. +func newTestGenesis() (bookkeeping.GenesisBalances, []basics.Address, []*crypto.SignatureSecrets) { + // irrelevant, but deterministic + sink, err := basics.UnmarshalChecksumAddress("YTPRLJ2KK2JRFSZZNAF57F3K5Y2KCG36FZ5OSYLW776JJGAUW5JXJBBD7Q") + if err != nil { + panic(err) + } + rewards, err := basics.UnmarshalChecksumAddress("242H5OXHUEBYCGGWB3CQ6AZAMQB5TMCWJGHCGQOZPEIVQJKOO7NZXUXDQA") + if err != nil { + panic(err) + } + + const count = 10 + addrs := make([]basics.Address, count) + secrets := make([]*crypto.SignatureSecrets, count) + accts := make(map[basics.Address]basics.AccountData) + + // 10 billion microalgos, across N accounts and pool and sink + amount := 10 * 1000000000 * 1000000 / uint64(count+2) + + for i := 0; i < count; i++ { + // Create deterministic addresses, so that output stays the same, run to run. + var seed crypto.Seed + seed[0] = byte(i) + secrets[i] = crypto.GenerateSignatureSecrets(seed) + addrs[i] = basics.Address(secrets[i].SignatureVerifier) + + adata := basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: amount}, + } + accts[addrs[i]] = adata + } + + accts[sink] = basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: amount}, + Status: basics.NotParticipating, + } + + accts[rewards] = basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: amount}, + } + + genBalances := bookkeeping.MakeGenesisBalances(accts, sink, rewards) + + return genBalances, addrs, secrets +} + type evalTestLedger struct { blocks map[basics.Round]bookkeeping.Block roundBalances map[basics.Round]map[basics.Address]basics.AccountData @@ -684,7 +732,6 @@ type evalTestLedger struct { latestTotals ledgercore.AccountTotals tracer logic.EvalTracer boxes map[string][]byte - voters map[basics.Round]*ledgercore.VotersForRound } // newTestLedger creates a in memory Ledger that is as realistic as @@ -894,9 +941,6 @@ func (ledger *evalTestLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeade } func (ledger *evalTestLedger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { - if v, ok := ledger.voters[rnd]; ok { - return v, nil - } return nil, errors.New("untested code path") } @@ -1401,178 +1445,3 @@ func TestExpiredAccountGeneration(t *testing.T) { require.Equal(t, crypto.VRFVerifier{}, recvAcct.SelectionID) require.Equal(t, merklesignature.Verifier{}.Commitment, recvAcct.StateProofID) } - -func TestEval_EndOfBlockStake(t *testing.T) { - partitiontest.PartitionTest(t) - - proto := protocol.ConsensusFuture - mainnetDigest, digestError := crypto.DigestFromString("YBQ4JWH4DW655UWXMBF6IVUOH5WQIGMHVQ333ZFWEC22WOJERLPQ") - require.NoError(t, digestError) - - var tests = []struct { - actual basics.MicroAlgos - expected basics.MicroAlgos - round basics.Round - genesisHash crypto.Digest - err string - }{ - // Normal round mainnet, matched stake => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "", - }, - // Normal round mainnet, mismatched stake (actual error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "StateProofOnlineTotalWeight wrong: {99} != {100}", - }, - // Normal round mainnet, mismatched stake (actual>expected) => error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 99}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "StateProofOnlineTotalWeight wrong: {100} != {99}", - }, - // Normal round mainnet, mismatched stake with max possible value for muldiv => error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "StateProofOnlineTotalWeight wrong: {100} != {18446744073709551615}", - }, - // Normal round mainnet, matched stake with max possible value for muldiv => no error - { - actual: basics.MicroAlgos{Raw: math.MaxUint64}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "", - }, - // Exception round mainnet, mismatched state (actual>expected) => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 99}, - round: basics.Round(27713280), - genesisHash: mainnetDigest, - err: "", - }, - // Exception round mainnet, mismatched state (actual no error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(27713280), - genesisHash: mainnetDigest, - err: "", - }, - // Exception round mainnet, mismatched stake with max possible value for muldiv => no error - { - actual: basics.MicroAlgos{Raw: math.MaxUint64 - 100}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(27713280), - genesisHash: mainnetDigest, - err: "", - }, - // Normal round non-mainnet, matched stake => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - err: "", - }, - // Normal round non-mainnet, mismatched stake (actual error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - err: "StateProofOnlineTotalWeight wrong: {99} != {100}", - }, - // Exception round non-mainnet, mismatched state (actual>expected) => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 99}, - round: basics.Round(27713280), - err: "StateProofOnlineTotalWeight wrong: {100} != {99}", - }, - // Exception round non-mainnet, mismatched state (actual no error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(27713280), - err: "StateProofOnlineTotalWeight wrong: {99} != {100}", - }, - // Exception round non-mainnet, mismatched stake with max possible value for muldiv => no error - { - actual: basics.MicroAlgos{Raw: math.MaxUint64 - 100}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(27713280), - err: "StateProofOnlineTotalWeight wrong: {18446744073709551515} != {18446744073709551615}", - }, - } - - for i, test := range tests { - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - params := config.Consensus[proto] - - genesisInitState, _, _ := ledgertesting.GenesisWithProto(2, proto) - - l := newTestLedger(t, bookkeeping.GenesisBalances{ - Balances: genesisInitState.Accounts, - FeeSink: testSinkAddr, - RewardsPool: testPoolAddr, - Timestamp: 0, - }) - // If genesis hash is non-nil in test case, override the test hash here - if !test.genesisHash.IsZero() { - l.genesisHash = test.genesisHash - } - l.voters = map[basics.Round]*ledgercore.VotersForRound{ - basics.Round(uint64(test.round) - params.StateProofVotersLookback): { - Tree: &merklearray.Tree{}, - TotalWeight: test.expected, - }, - } - - genesisBlockHeader, err := l.BlockHdr(0) - require.NoError(t, err) - newBlock := bookkeeping.MakeBlock(genesisBlockHeader) - newBlock.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ - protocol.StateProofBasic: { - StateProofOnlineTotalWeight: test.actual, - }, - } - newBlock.BlockHeader.Round = test.round - - pcow := mockLedger{balanceMap: map[basics.Address]basics.AccountData{}} - mods := ledgercore.StateDelta{Hdr: &newBlock.BlockHeader} - cow := &roundCowState{ - lookupParent: &pcow, - mods: mods, - } - eval := BlockEvaluator{ - state: cow, - validate: true, - prevHeader: genesisBlockHeader, - proto: params, - genesisHash: l.genesisHash, - block: newBlock, - l: l, - } - - err = eval.endOfBlock() - if len(test.err) == 0 { - require.NoError(t, err) - } else { - require.ErrorContains(t, err, test.err) - } - }) - } - -} From 948f08b6086dab51d079e8cf61b2d32f65d15465 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:07:29 -0500 Subject: [PATCH 015/218] config: move crash and stateproof DB defaults to hot dir (#5817) --- config/config_test.go | 124 +++++++++++++++++++++++++++++++++++++--- config/localTemplate.go | 57 +++++++++++++++--- node/follower_node.go | 2 +- node/node.go | 2 +- node/node_test.go | 6 +- 5 files changed, 170 insertions(+), 21 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index 87b4cc4d43..ef58bffb9d 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -675,6 +675,12 @@ func TestEnsureAbsDir(t *testing.T) { require.Equal(t, testDirectory+"/myGenesisID", t2Abs) } +type tLogger struct{ t *testing.T } + +func (l tLogger) Infof(fmts string, args ...interface{}) { + l.t.Logf(fmts, args...) +} + // TestEnsureAndResolveGenesisDirs confirms that paths provided in the config are resolved to absolute paths and are created if relevant func TestEnsureAndResolveGenesisDirs(t *testing.T) { partitiontest.PartitionTest(t) @@ -689,7 +695,7 @@ func TestEnsureAndResolveGenesisDirs(t *testing.T) { cfg.StateproofDir = filepath.Join(testDirectory, "/RELATIVEPATHS/../RELATIVE/../custom_stateproof") cfg.CatchpointDir = filepath.Join(testDirectory, "custom_catchpoint") - paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID") + paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t}) require.NoError(t, err) // confirm that the paths are absolute, and contain the genesisID @@ -711,7 +717,7 @@ func TestEnsureAndResolveGenesisDirs_hierarchy(t *testing.T) { cfg := GetDefaultLocal() testDirectory := t.TempDir() - paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID") + paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t}) require.NoError(t, err) // confirm that if only the root is specified, it is used for all directories require.Equal(t, testDirectory+"/myGenesisID", paths.TrackerGenesisDir) @@ -731,21 +737,125 @@ func TestEnsureAndResolveGenesisDirs_hierarchy(t *testing.T) { cold := filepath.Join(testDirectory, "cold") cfg.HotDataDir = hot cfg.ColdDataDir = cold - paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID") + paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t}) require.NoError(t, err) // confirm that if hot/cold are specified, hot/cold are used for appropriate directories require.Equal(t, hot+"/myGenesisID", paths.TrackerGenesisDir) require.DirExists(t, paths.TrackerGenesisDir) require.Equal(t, cold+"/myGenesisID", paths.BlockGenesisDir) require.DirExists(t, paths.BlockGenesisDir) - require.Equal(t, cold+"/myGenesisID", paths.CrashGenesisDir) + require.Equal(t, hot+"/myGenesisID", paths.CrashGenesisDir) require.DirExists(t, paths.CrashGenesisDir) - require.Equal(t, cold+"/myGenesisID", paths.StateproofGenesisDir) + require.Equal(t, hot+"/myGenesisID", paths.StateproofGenesisDir) require.DirExists(t, paths.StateproofGenesisDir) require.Equal(t, cold+"/myGenesisID", paths.CatchpointGenesisDir) require.DirExists(t, paths.CatchpointGenesisDir) } +func TestEnsureAndResolveGenesisDirs_migrate(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := GetDefaultLocal() + testDirectory := t.TempDir() + cfg.HotDataDir = filepath.Join(testDirectory, "hot") + cfg.ColdDataDir = filepath.Join(testDirectory, "cold") + coldDir := filepath.Join(cfg.ColdDataDir, "myGenesisID") + hotDir := filepath.Join(cfg.HotDataDir, "myGenesisID") + err := os.MkdirAll(coldDir, 0755) + require.NoError(t, err) + // put a crash.sqlite file in the ColdDataDir + err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite"), []byte("test"), 0644) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite-shm"), []byte("test"), 0644) + require.NoError(t, err) + // put a stateproof.sqlite file in the ColdDataDir + err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite"), []byte("test"), 0644) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite-wal"), []byte("test"), 0644) + require.NoError(t, err) + // Resolve + paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t}) + require.NoError(t, err) + // Confirm that crash.sqlite was moved to HotDataDir + require.DirExists(t, paths.CrashGenesisDir) + require.Equal(t, hotDir, paths.CrashGenesisDir) + require.NoFileExists(t, filepath.Join(coldDir, "crash.sqlite")) + require.NoFileExists(t, filepath.Join(coldDir, "crash.sqlite-shm")) + require.FileExists(t, filepath.Join(hotDir, "crash.sqlite")) + require.FileExists(t, filepath.Join(hotDir, "crash.sqlite-shm")) + // Confirm that stateproof.sqlite was moved to HotDataDir + require.DirExists(t, paths.StateproofGenesisDir) + require.Equal(t, hotDir, paths.StateproofGenesisDir) + require.NoFileExists(t, filepath.Join(coldDir, "stateproof.sqlite")) + require.NoFileExists(t, filepath.Join(coldDir, "stateproof.sqlite-wal")) + require.FileExists(t, filepath.Join(hotDir, "stateproof.sqlite")) + require.FileExists(t, filepath.Join(hotDir, "stateproof.sqlite-wal")) +} + +func TestEnsureAndResolveGenesisDirs_migrateCrashFail(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := GetDefaultLocal() + testDirectory := t.TempDir() + cfg.HotDataDir = filepath.Join(testDirectory, "hot") + cfg.ColdDataDir = filepath.Join(testDirectory, "cold") + coldDir := filepath.Join(cfg.ColdDataDir, "myGenesisID") + hotDir := filepath.Join(cfg.HotDataDir, "myGenesisID") + err := os.MkdirAll(coldDir, 0755) + require.NoError(t, err) + err = os.MkdirAll(hotDir, 0755) + require.NoError(t, err) + // put a crash.sqlite file in the ColdDataDir + err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite"), []byte("test"), 0644) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite-shm"), []byte("test"), 0644) + require.NoError(t, err) + // also put a crash.sqlite file in the HotDataDir + err = os.WriteFile(filepath.Join(hotDir, "crash.sqlite"), []byte("test"), 0644) + require.NoError(t, err) + // Resolve + paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t}) + require.Error(t, err) + require.Empty(t, paths) + // Confirm that crash.sqlite was not moved to HotDataDir + require.FileExists(t, filepath.Join(coldDir, "crash.sqlite")) + require.FileExists(t, filepath.Join(coldDir, "crash.sqlite-shm")) + require.FileExists(t, filepath.Join(hotDir, "crash.sqlite")) + require.NoFileExists(t, filepath.Join(hotDir, "crash.sqlite-shm")) +} + +func TestEnsureAndResolveGenesisDirs_migrateSPFail(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := GetDefaultLocal() + testDirectory := t.TempDir() + cfg.HotDataDir = filepath.Join(testDirectory, "hot") + cfg.ColdDataDir = filepath.Join(testDirectory, "cold") + coldDir := filepath.Join(cfg.ColdDataDir, "myGenesisID") + hotDir := filepath.Join(cfg.HotDataDir, "myGenesisID") + err := os.MkdirAll(coldDir, 0755) + require.NoError(t, err) + err = os.MkdirAll(hotDir, 0755) + require.NoError(t, err) + // put a stateproof.sqlite file in the ColdDataDir + err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite"), []byte("test"), 0644) + require.NoError(t, err) + err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite-wal"), []byte("test"), 0644) + require.NoError(t, err) + // also put a stateproof.sqlite-wal file in the HotDataDir + err = os.WriteFile(filepath.Join(hotDir, "stateproof.sqlite-wal"), []byte("test"), 0644) + require.NoError(t, err) + // Resolve + paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t}) + require.Error(t, err) + require.Empty(t, paths) + // Confirm that stateproof.sqlite was not moved to HotDataDir + require.FileExists(t, filepath.Join(coldDir, "stateproof.sqlite")) + require.FileExists(t, filepath.Join(coldDir, "stateproof.sqlite-wal")) + require.NoFileExists(t, filepath.Join(hotDir, "stateproof.sqlite")) + require.FileExists(t, filepath.Join(hotDir, "stateproof.sqlite-wal")) +} + // TestEnsureAndResolveGenesisDirsError confirms that if a path can't be created, an error is returned func TestEnsureAndResolveGenesisDirsError(t *testing.T) { partitiontest.PartitionTest(t) @@ -761,7 +871,7 @@ func TestEnsureAndResolveGenesisDirsError(t *testing.T) { cfg.CatchpointDir = filepath.Join(testDirectory, "custom_catchpoint") // first try an error with an empty root dir - paths, err := cfg.EnsureAndResolveGenesisDirs("", "myGenesisID") + paths, err := cfg.EnsureAndResolveGenesisDirs("", "myGenesisID", tLogger{t: t}) require.Empty(t, paths) require.Error(t, err) require.Contains(t, err.Error(), "rootDir is required") @@ -769,7 +879,7 @@ func TestEnsureAndResolveGenesisDirsError(t *testing.T) { require.NoError(t, os.Chmod(testDirectory, 0200)) // now try an error with a root dir that can't be written to - paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID") + paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t}) require.Empty(t, paths) require.Error(t, err) require.Contains(t, err.Error(), "permission denied") diff --git a/config/localTemplate.go b/config/localTemplate.go index 6748801209..07a9bf5eb0 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -111,13 +111,13 @@ type Local struct { // For isolation, the node will create a subdirectory in this location, named by the genesis-id of the network. // If not specified, the node will use the ColdDataDir. CatchpointDir string `version[31]:""` - // StateproofDir is an optional directory to store stateproof data. + // StateproofDir is an optional directory to persist state about observed and issued state proof messages. // For isolation, the node will create a subdirectory in this location, named by the genesis-id of the network. - // If not specified, the node will use the ColdDataDir. + // If not specified, the node will use the HotDataDir. StateproofDir string `version[31]:""` - // CrashDBDir is an optional directory to store the crash database. + // CrashDBDir is an optional directory to persist agreement's consensus participation state. // For isolation, the node will create a subdirectory in this location, named by the genesis-id of the network. - // If not specified, the node will use the ColdDataDir. + // If not specified, the node will use the HotDataDir CrashDBDir string `version[31]:""` // LogFileDir is an optional directory to store the log, node.log @@ -785,9 +785,13 @@ func (cfg *Local) ResolveLogPaths(rootDir string) (liveLog, archive string) { return liveLog, archive } +type logger interface { + Infof(format string, args ...interface{}) +} + // EnsureAndResolveGenesisDirs will resolve the supplied config paths to absolute paths, and will create the genesis directories of each // returns a ResolvedGenesisDirs struct with the resolved paths for use during runtime -func (cfg *Local) EnsureAndResolveGenesisDirs(rootDir, genesisID string) (ResolvedGenesisDirs, error) { +func (cfg *Local) EnsureAndResolveGenesisDirs(rootDir, genesisID string, logger logger) (ResolvedGenesisDirs, error) { var resolved ResolvedGenesisDirs var err error if rootDir != "" { @@ -843,27 +847,62 @@ func (cfg *Local) EnsureAndResolveGenesisDirs(rootDir, genesisID string) (Resolv } else { resolved.CatchpointGenesisDir = resolved.ColdGenesisDir } - // if StateproofDir is not set, use ColdDataDir + // if StateproofDir is not set, use HotDataDir if cfg.StateproofDir != "" { resolved.StateproofGenesisDir, err = ensureAbsGenesisDir(cfg.StateproofDir, genesisID) if err != nil { return ResolvedGenesisDirs{}, err } } else { - resolved.StateproofGenesisDir = resolved.ColdGenesisDir + resolved.StateproofGenesisDir = resolved.HotGenesisDir + // if separate HotDataDir and ColdDataDir was configured, but StateproofDir was not configured + if resolved.ColdGenesisDir != resolved.HotGenesisDir { + // move existing stateproof DB files from ColdDataDir to HotDataDir + moveErr := moveDirIfExists(logger, resolved.ColdGenesisDir, resolved.HotGenesisDir, StateProofFileName, StateProofFileName+"-shm", StateProofFileName+"-wal") + if moveErr != nil { + return ResolvedGenesisDirs{}, fmt.Errorf("error moving stateproof DB files from ColdDataDir %s to HotDataDir %s: %v", resolved.ColdGenesisDir, resolved.HotGenesisDir, moveErr) + } + } } - // if CrashDBDir is not set, use ColdDataDir + // if CrashDBDir is not set, use HotDataDir if cfg.CrashDBDir != "" { resolved.CrashGenesisDir, err = ensureAbsGenesisDir(cfg.CrashDBDir, genesisID) if err != nil { return ResolvedGenesisDirs{}, err } } else { - resolved.CrashGenesisDir = resolved.ColdGenesisDir + resolved.CrashGenesisDir = resolved.HotGenesisDir + // if separate HotDataDir and ColdDataDir was configured, but CrashDBDir was not configured + if resolved.ColdGenesisDir != resolved.HotGenesisDir { + // move existing crash DB files from ColdDataDir to HotDataDir + moveErr := moveDirIfExists(logger, resolved.ColdGenesisDir, resolved.HotGenesisDir, CrashFilename, CrashFilename+"-shm", CrashFilename+"-wal") + if moveErr != nil { + return ResolvedGenesisDirs{}, fmt.Errorf("error moving crash DB files from ColdDataDir %s to HotDataDir %s: %v", resolved.ColdGenesisDir, resolved.HotGenesisDir, moveErr) + } + } } return resolved, nil } +func moveDirIfExists(logger logger, srcdir, dstdir string, files ...string) error { + // first, check if any files already exist in dstdir, and quit if so + for _, file := range files { + if _, err := os.Stat(filepath.Join(dstdir, file)); err == nil { + return fmt.Errorf("destination file %s already exists, not overwriting", filepath.Join(dstdir, file)) + } + } + // then, check if any files exist in srcdir, and move them to dstdir + for _, file := range files { + if _, err := os.Stat(filepath.Join(srcdir, file)); err == nil { + if err := os.Rename(filepath.Join(srcdir, file), filepath.Join(dstdir, file)); err != nil { + return fmt.Errorf("failed to move file %s from %s to %s: %v", file, srcdir, dstdir, err) + } + logger.Infof("Moved DB file %s from ColdDataDir %s to HotDataDir %s", file, srcdir, dstdir) + } + } + return nil +} + // AdjustConnectionLimits updates RestConnectionsSoftLimit, RestConnectionsHardLimit, IncomingConnectionsLimit // if requiredFDs greater than maxFDs func (cfg *Local) AdjustConnectionLimits(requiredFDs, maxFDs uint64) bool { diff --git a/node/follower_node.go b/node/follower_node.go index 66790b1291..c61c379577 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -83,7 +83,7 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo node.genesisHash = genesis.Hash() node.devMode = genesis.DevMode var err error - node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID()) + node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID(), log) if err != nil { return nil, err } diff --git a/node/node.go b/node/node.go index 4c18ad1d51..e1f79907c8 100644 --- a/node/node.go +++ b/node/node.go @@ -183,7 +183,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd node.devMode = genesis.DevMode node.config = cfg var err error - node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID()) + node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID(), log) if err != nil { return nil, err } diff --git a/node/node_test.go b/node/node_test.go index c905fa78da..55cbae9368 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -598,7 +598,7 @@ func TestConfiguredDataDirs(t *testing.T) { 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")) + require.FileExists(t, filepath.Join(testDirHot, genesis.ID(), "stateproof.sqlite")) // confirm cold data dir exists and contains a genesis dir require.DirExists(t, filepath.Join(testDirCold, genesis.ID())) @@ -609,8 +609,8 @@ func TestConfiguredDataDirs(t *testing.T) { // 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")) + // confirm the agreement crash DB is in the genesis dir of hot data dir + require.FileExists(t, filepath.Join(testDirHot, 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 From 70405008b894bb12c3c1f31862d339f65bb903ca Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:05:57 -0500 Subject: [PATCH 016/218] catchup: use specialized backoff behavior for follower mode (#5836) --- catchup/service.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/catchup/service.go b/catchup/service.go index d755a3d559..8277324eaf 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -49,6 +49,8 @@ const uncapParallelDownloadRate = time.Second // this should be at least the number of relays const catchupRetryLimit = 500 +const followLatestBackoff = 50 * time.Millisecond + // ErrSyncRoundInvalid is returned when the sync round requested is behind the current ledger round var ErrSyncRoundInvalid = errors.New("requested sync round cannot be less than the latest round") @@ -93,6 +95,12 @@ type Service struct { prevBlockFetchTime time.Time blockValidationPool execpool.BacklogPool + // followLatest is set to true if this is a follower node: meaning there is no + // agreement service to follow the latest round, so catchup continuously runs, + // polling for new blocks as they appear. This enables a different behavior + // to avoid aborting the catchup service once you get to the tip of the chain. + followLatest bool + // suspendForLedgerOps defines whether we've run into a state where the ledger is currently busy writing the // catchpoint file or flushing accounts. If so, we want to suspend the catchup process until the catchpoint file writing is complete, // and resume from there without stopping the catchup timer. @@ -131,6 +139,7 @@ func MakeService(log logging.Logger, config config.Local, net network.GossipNode s = &Service{} s.cfg = config + s.followLatest = s.cfg.EnableFollowMode s.ledger = ledger s.net = net s.auth = auth @@ -321,11 +330,18 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo failureRank = peerRankNoBlockForRound // remote peer doesn't have the block, try another peer // quit if the the same peer peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times - if count := peerErrors[peer]; count > errNoBlockForRoundThreshold { - s.log.Infof("fetchAndWrite(%d): remote peers do not have the block. Quitting", r) - return false + if s.followLatest { + // back off between retries to allow time for the next block to appear; + // this will provide 25s (catchupRetryLimit * followLatestBackoff) of + // polling when continuously running catchup instead of agreement. + time.Sleep(followLatestBackoff) + } else { + if count := peerErrors[peer]; count > errNoBlockForRoundThreshold { + s.log.Infof("fetchAndWrite(%d): remote peers do not have the block. Quitting", r) + return false + } + peerErrors[peer]++ } - peerErrors[peer]++ } s.log.Debugf("fetchAndWrite(%v): Could not fetch: %v (attempt %d)", r, err, i) peerSelector.rankPeer(psp, failureRank) From ff0ee44afb1c44c6704770a5bbd431cebe0c0b34 Mon Sep 17 00:00:00 2001 From: bbroder-uji <92406993+bbroder-uji@users.noreply.github.com> Date: Thu, 16 Nov 2023 14:06:25 -0500 Subject: [PATCH 017/218] statetrie: nibbles (#5759) --- crypto/statetrie/nibbles/nibbles.go | 161 +++++++++++++++++ crypto/statetrie/nibbles/nibbles_test.go | 218 +++++++++++++++++++++++ 2 files changed, 379 insertions(+) create mode 100644 crypto/statetrie/nibbles/nibbles.go create mode 100644 crypto/statetrie/nibbles/nibbles_test.go diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go new file mode 100644 index 0000000000..8a8409b6b0 --- /dev/null +++ b/crypto/statetrie/nibbles/nibbles.go @@ -0,0 +1,161 @@ +// 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 nibbles + +import ( + "bytes" + "errors" +) + +// Nibbles are 4-bit values stored in an 8-bit byte arrays +type Nibbles []byte + +const ( + // oddIndicator for serialization when the last nibble in a byte array + // is not part of the nibble array. + oddIndicator = 0x01 + // evenIndicator for when it is. + evenIndicator = 0x03 +) + +// Pack the nibble array into a byte array. +// Return the byte array and a bool indicating if the last byte is a full byte or +// only the high 4 bits are part of the encoding +// the last four bits of a oddLength byte encoding will always be zero. +// Allocates a new byte slice. +// +// [0x1, 0x2, 0x3] -> [0x12, 0x30], true +// [0x1, 0x2, 0x3, 0x4] -> [0x12, 0x34], false +// [0x1] -> [0x10], true +// [] -> [], false +func Pack(nyb Nibbles) ([]byte, bool) { + length := len(nyb) + data := make([]byte, length/2+length%2, length/2+length%2+1) + for i := 0; i < length; i++ { + if i%2 == 0 { + data[i/2] = nyb[i] << 4 + } else { + data[i/2] = data[i/2] | nyb[i] + } + } + + return data, length%2 != 0 +} + +// Equal returns true if the two nibble arrays are equal +// [0x1, 0x2, 0x3], [0x1, 0x2, 0x3] -> true +// [0x1, 0x2, 0x3], [0x1, 0x2, 0x4] -> false +// [0x1, 0x2, 0x3], [0x1] -> false +// [0x1, 0x2, 0x3], [0x1, 0x2, 0x3, 0x4] -> false +// [], [] -> true +// [], [0x1] -> false +func Equal(nyb1 Nibbles, nyb2 Nibbles) bool { + return bytes.Equal(nyb1, nyb2) +} + +// ShiftLeft returns a slice of nyb1 that contains the Nibbles after the first +// numNibbles +func ShiftLeft(nyb1 Nibbles, numNibbles int) Nibbles { + if numNibbles <= 0 { + return nyb1 + } + if numNibbles > len(nyb1) { + return nyb1[:0] + } + + return nyb1[numNibbles:] +} + +// SharedPrefix returns a slice from nyb1 that contains the shared prefix +// between nyb1 and nyb2 +func SharedPrefix(nyb1 Nibbles, nyb2 Nibbles) Nibbles { + minLength := len(nyb1) + if len(nyb2) < minLength { + minLength = len(nyb2) + } + for i := 0; i < minLength; i++ { + if nyb1[i] != nyb2[i] { + return nyb1[:i] + } + } + return nyb1[:minLength] +} + +// Serialize returns a byte array that represents the Nibbles +// an empty nibble array is serialized as a single byte with value 0x3 +// as the empty nibble is considered to be full width +// +// [0x1, 0x2, 0x3] -> [0x12, 0x30, 0x01] +// [0x1, 0x2, 0x3, 0x4] -> [0x12, 0x34, 0x03] +// [] -> [0x03] +func Serialize(nyb Nibbles) (data []byte) { + p, h := Pack(nyb) + if h { + // 0x01 is the odd length indicator + return append(p, oddIndicator) + } + // 0x03 is the even length indicator + return append(p, evenIndicator) +} + +// Deserialize returns a nibble array from the byte array. +func Deserialize(encoding []byte) (Nibbles, error) { + var ns Nibbles + length := len(encoding) + if length == 0 { + return nil, errors.New("invalid encoding") + } + if encoding[length-1] == oddIndicator { + if length == 1 { + return nil, errors.New("invalid encoding") + } + ns = makeNibbles(encoding[:length-1], true) + } else if encoding[length-1] == evenIndicator { + ns = makeNibbles(encoding[:length-1], false) + } else { + return nil, errors.New("invalid encoding") + } + return ns, nil +} + +// makeNibbles returns a nibble array from the byte array. If oddLength is true, +// the last 4 bits of the last byte of the array are ignored. +// +// [0x12, 0x30], true -> [0x1, 0x2, 0x3] +// [0x12, 0x34], false -> [0x1, 0x2, 0x3, 0x4] +// [0x12, 0x34], true -> [0x1, 0x2, 0x3] <-- last byte last 4 bits ignored +// [], false -> [] +// never to be called with [], true +// Allocates a new byte slice. +func makeNibbles(data []byte, oddLength bool) Nibbles { + length := len(data) * 2 + if oddLength { + length = length - 1 + } + ns := make([]byte, length) + + j := 0 + for i := 0; i < length; i++ { + if i%2 == 0 { + ns[i] = data[j] >> 4 + } else { + ns[i] = data[j] & 0x0f + j++ + } + } + return ns +} diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go new file mode 100644 index 0000000000..c088f1dd8a --- /dev/null +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -0,0 +1,218 @@ +// 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 nibbles + +import ( + "bytes" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestNibblesRandom(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + seed := time.Now().UnixNano() + localRand := rand.New(rand.NewSource(seed)) + defer func() { + if t.Failed() { + t.Logf("The seed was %d", seed) + } + }() + + for i := 0; i < 1_000; i++ { + length := localRand.Intn(8192) + 1 + data := make([]byte, length) + localRand.Read(data) + half := localRand.Intn(2) == 0 // half of the time, we have an odd number of nibbles + if half && localRand.Intn(2) == 0 { + data[len(data)-1] &= 0xf0 // sometimes clear the last nibble, sometimes do not + } + nibbles := makeNibbles(data, half) + + data2 := Serialize(nibbles) + nibbles2, err := Deserialize(data2) + require.NoError(t, err) + require.Equal(t, nibbles, nibbles2) + + if half { + data[len(data)-1] &= 0xf0 // clear last nibble + } + packed, odd := Pack(nibbles) + require.Equal(t, odd, half) + require.Equal(t, packed, data) + unpacked := makeNibbles(packed, odd) + require.Equal(t, nibbles, unpacked) + + packed, odd = Pack(nibbles2) + require.Equal(t, odd, half) + require.Equal(t, packed, data) + unpacked = makeNibbles(packed, odd) + require.Equal(t, nibbles2, unpacked) + } +} + +func TestNibblesDeserialize(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + enc := []byte{0x01} + _, err := Deserialize(enc) + require.Error(t, err, "should return invalid encoding error") +} + +func TestNibbles(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + sampleNibbles := []Nibbles{ + {0x0, 0x1, 0x2, 0x3, 0x4}, + {0x4, 0x1, 0x2, 0x3, 0x4}, + {0x0, 0x0, 0x2, 0x3, 0x5}, + {0x0, 0x1, 0x2, 0x3, 0x4, 0x5}, + {}, + {0x1}, + } + + sampleNibblesPacked := [][]byte{ + {0x01, 0x23, 0x40}, + {0x41, 0x23, 0x40}, + {0x00, 0x23, 0x50}, + {0x01, 0x23, 0x45}, + {}, + {0x10}, + } + + sampleNibblesShifted1 := []Nibbles{ + {0x1, 0x2, 0x3, 0x4}, + {0x1, 0x2, 0x3, 0x4}, + {0x0, 0x2, 0x3, 0x5}, + {0x1, 0x2, 0x3, 0x4, 0x5}, + {}, + {}, + } + + sampleNibblesShifted2 := []Nibbles{ + {0x2, 0x3, 0x4}, + {0x2, 0x3, 0x4}, + {0x2, 0x3, 0x5}, + {0x2, 0x3, 0x4, 0x5}, + {}, + {}, + } + + for i, n := range sampleNibbles { + b, oddLength := Pack(n) + if oddLength { + // require that oddLength packs returns a byte slice with the last nibble set to 0x0 + require.Equal(t, b[len(b)-1]&0x0f == 0x00, true) + } + + require.Equal(t, oddLength == (len(n)%2 == 1), true) + require.Equal(t, bytes.Equal(b, sampleNibblesPacked[i]), true) + + unp := makeNibbles(b, oddLength) + require.Equal(t, bytes.Equal(unp, n), true) + + } + for i, n := range sampleNibbles { + require.Equal(t, bytes.Equal(ShiftLeft(n, -2), sampleNibbles[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, -1), sampleNibbles[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, 0), sampleNibbles[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, 1), sampleNibblesShifted1[i]), true) + require.Equal(t, bytes.Equal(ShiftLeft(n, 2), sampleNibblesShifted2[i]), true) + } + + sampleSharedNibbles := [][]Nibbles{ + {{0x0, 0x1, 0x2, 0x9, 0x2}, {0x0, 0x1, 0x2}}, + {{0x4, 0x1}, {0x4, 0x1}}, + {{0x9, 0x2, 0x3}, {}}, + {{0x0}, {0x0}}, + {{}, {}}, + } + for i, n := range sampleSharedNibbles { + shared := SharedPrefix(n[0], sampleNibbles[i]) + require.Equal(t, bytes.Equal(shared, n[1]), true) + shared = SharedPrefix(sampleNibbles[i], n[0]) + require.Equal(t, bytes.Equal(shared, n[1]), true) + } + + sampleSerialization := []Nibbles{ + {0x0, 0x1, 0x2, 0x9, 0x2}, + {0x4, 0x1}, + {0x4, 0x1, 0x4, 0xf}, + {0x4, 0x1, 0x4, 0xf, 0x0}, + {0x9, 0x2, 0x3}, + {}, + {0x05}, + {}, + } + + for _, n := range sampleSerialization { + nbytes := Serialize(n) + n2, err := Deserialize(nbytes) + require.NoError(t, err) + require.True(t, bytes.Equal(n, n2)) + require.Equal(t, len(nbytes), len(n)/2+len(n)%2+1, fmt.Sprintf("nbytes: %v, n: %v", nbytes, n)) + if len(n)%2 == 0 { + require.Equal(t, nbytes[len(nbytes)-1], uint8(evenIndicator)) + } else { + require.Equal(t, nbytes[len(nbytes)-1], uint8(oddIndicator)) + require.Equal(t, nbytes[len(nbytes)-2]&0x0F, uint8(0)) + } + } + + makeNibblesTestExpected := Nibbles{0x0, 0x1, 0x2, 0x9, 0x2} + makeNibblesTestData := []byte{0x01, 0x29, 0x20} + mntr := makeNibbles(makeNibblesTestData, true) + require.Equal(t, bytes.Equal(mntr, makeNibblesTestExpected), true) + makeNibblesTestExpectedFW := Nibbles{0x0, 0x1, 0x2, 0x9, 0x2, 0x0} + mntr2 := makeNibbles(makeNibblesTestData, false) + require.Equal(t, bytes.Equal(mntr2, makeNibblesTestExpectedFW), true) + + sampleEqualFalse := [][]Nibbles{ + {{0x0, 0x1, 0x2, 0x9, 0x2}, {0x0, 0x1, 0x2, 0x9}}, + {{0x0, 0x1, 0x2, 0x9}, {0x0, 0x1, 0x2, 0x9, 0x2}}, + {{0x0, 0x1, 0x2, 0x9, 0x2}, {}}, + {{}, {0x0, 0x1, 0x2, 0x9, 0x2}}, + {{0x0}, {}}, + {{}, {0x0}}, + {{}, {0x1}}, + } + for _, n := range sampleEqualFalse { + ds := Serialize(n[0]) + us, e := Deserialize(ds) + require.NoError(t, e) + require.Equal(t, Equal(n[0], us), true) + require.Equal(t, Equal(n[0], n[0]), true) + require.Equal(t, Equal(us, n[0]), true) + require.Equal(t, Equal(n[0], n[1]), false) + require.Equal(t, Equal(us, n[1]), false) + require.Equal(t, Equal(n[1], n[0]), false) + require.Equal(t, Equal(n[1], us), false) + } + + _, e := Deserialize([]byte{}) + require.Error(t, e) + _, e = Deserialize([]byte{0x02}) + require.Error(t, e) +} From 07e0a1ba9399c01e06b38720b5f5aec1080f5414 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:32:56 -0500 Subject: [PATCH 018/218] catchup: increase followLatestBackoff to 100ms (#5838) --- catchup/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/catchup/service.go b/catchup/service.go index 8277324eaf..bcf204b134 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -49,7 +49,7 @@ const uncapParallelDownloadRate = time.Second // this should be at least the number of relays const catchupRetryLimit = 500 -const followLatestBackoff = 50 * time.Millisecond +const followLatestBackoff = 100 * time.Millisecond // ErrSyncRoundInvalid is returned when the sync round requested is behind the current ledger round var ErrSyncRoundInvalid = errors.New("requested sync round cannot be less than the latest round") @@ -332,7 +332,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo // quit if the the same peer peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times if s.followLatest { // back off between retries to allow time for the next block to appear; - // this will provide 25s (catchupRetryLimit * followLatestBackoff) of + // this will provide 50s (catchupRetryLimit * followLatestBackoff) of // polling when continuously running catchup instead of agreement. time.Sleep(followLatestBackoff) } else { From 06790fe89f30cbeeb52695c31bca6297530736c5 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 17 Nov 2023 10:15:53 -0500 Subject: [PATCH 019/218] Chore: Remove one allocate per hash by using generics (#5829) --- cmd/goal/application.go | 8 +++--- cmd/goal/interact.go | 4 +-- cmd/goal/tealsign.go | 2 +- crypto/hashes_test.go | 1 - crypto/util.go | 4 +-- .../algod/api/server/v2/test/handlers_test.go | 6 ++-- daemon/algod/api/server/v2/test/helpers.go | 3 +- data/account/participation.go | 4 +-- data/account/participation_test.go | 10 +++++++ data/bookkeeping/genesis_test.go | 28 +++++++++++++++++++ data/transactions/logic/program.go | 2 +- 11 files changed, 54 insertions(+), 18 deletions(-) diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 8d442bf5cd..2aebe89de8 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -488,7 +488,7 @@ var createAppCmd = &cobra.Command{ reportErrorf(errorBroadcastingTX, err2) } - reportInfof("Attempting to create app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + reportInfof("Attempting to create app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), logic.HashProgram(clearProg)) reportInfof("Issued transaction from account %s, txid %s (fee %d)", tx.Sender, txid, tx.Fee.Raw) if !noWaitAfterSend { @@ -563,7 +563,7 @@ var updateAppCmd = &cobra.Command{ reportErrorf(errorBroadcastingTX, err2) } - reportInfof("Attempting to update app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + reportInfof("Attempting to update app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), logic.HashProgram(clearProg)) reportInfof("Issued transaction from account %s, txid %s (fee %d)", tx.Sender, txid, tx.Fee.Raw) if !noWaitAfterSend { @@ -1455,9 +1455,9 @@ var methodAppCmd = &cobra.Command{ // Report tx details to user if methodCreatesApp { - reportInfof("Attempting to create app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + reportInfof("Attempting to create app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), logic.HashProgram(clearProg)) } else if onCompletionEnum == transactions.UpdateApplicationOC { - reportInfof("Attempting to update app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + reportInfof("Attempting to update app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), logic.HashProgram(clearProg)) } reportInfof("Issued %d transaction(s):", len(signedTxnGroup)) diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go index 825d74388f..ca79daf0d4 100644 --- a/cmd/goal/interact.go +++ b/cmd/goal/interact.go @@ -625,9 +625,9 @@ var appExecuteCmd = &cobra.Command{ } if appIdx == 0 { - reportInfof("Attempting to create app (global ints %d, global blobs %d, local ints %d, local blobs %d, approval size %d, hash %v; clear size %d, hash %v)", globalSchema.NumUint, globalSchema.NumByteSlice, localSchema.NumUint, localSchema.NumByteSlice, len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + reportInfof("Attempting to create app (global ints %d, global blobs %d, local ints %d, local blobs %d, approval size %d, hash %v; clear size %d, hash %v)", globalSchema.NumUint, globalSchema.NumByteSlice, localSchema.NumUint, localSchema.NumByteSlice, len(approvalProg), logic.HashProgram(approvalProg), len(clearProg), crypto.HashObj(logic.Program(clearProg))) } else if onCompletion == transactions.UpdateApplicationOC { - reportInfof("Attempting to update app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), crypto.HashObj(logic.Program(clearProg))) + reportInfof("Attempting to update app (approval size %d, hash %v; clear size %d, hash %v)", len(approvalProg), crypto.HashObj(logic.Program(approvalProg)), len(clearProg), logic.HashProgram(clearProg)) } reportInfof("Issued transaction from account %s, txid %s (fee %d)", tx.Sender, txid, tx.Fee.Raw) diff --git a/cmd/goal/tealsign.go b/cmd/goal/tealsign.go index 9d9f144da6..45fbd3a988 100644 --- a/cmd/goal/tealsign.go +++ b/cmd/goal/tealsign.go @@ -139,7 +139,7 @@ The base64 encoding of the signature will always be printed to stdout. Optionall reportErrorf(tealsignEmptyLogic) } - progHash = crypto.HashObj(logic.Program(stxn.Lsig.Logic)) + progHash = logic.HashProgram(stxn.Lsig.Logic) } else { // Otherwise, the contract address is the logic hash parsedAddr, err := basics.UnmarshalChecksumAddress(contractAddr) diff --git a/crypto/hashes_test.go b/crypto/hashes_test.go index dd4b8c3bd7..9f8b57fe3a 100644 --- a/crypto/hashes_test.go +++ b/crypto/hashes_test.go @@ -53,7 +53,6 @@ func TestHashSum(t *testing.T) { dgst := HashObj(TestingHashable{}) a.Equal(GenericHashObj(h, TestingHashable{}), dgst[:]) - } func TestEmptyHash(t *testing.T) { diff --git a/crypto/util.go b/crypto/util.go index 60bb12aef0..cc567f3998 100644 --- a/crypto/util.go +++ b/crypto/util.go @@ -35,7 +35,7 @@ type Hashable interface { } // HashRep appends the correct hashid before the message to be hashed. -func HashRep(h Hashable) []byte { +func HashRep[H Hashable](h H) []byte { hashid, data := h.ToBeHashed() return append([]byte(hashid), data...) } @@ -86,7 +86,7 @@ func Hash(data []byte) Digest { } // HashObj computes a hash of a Hashable object and its type -func HashObj(h Hashable) Digest { +func HashObj[H Hashable](h H) Digest { return Hash(HashRep(h)) } diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index d8e086ff32..aa5a7492b6 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -22,7 +22,6 @@ import ( "encoding/json" "errors" "fmt" - "golang.org/x/sync/semaphore" "io" "math" "net" @@ -33,6 +32,8 @@ import ( "testing" "time" + "golang.org/x/sync/semaphore" + "github.com/algorand/go-algorand/daemon/algod/api/server" "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -272,8 +273,7 @@ func addBlockHelper(t *testing.T) (v2.Handlers, echo.Context, *httptest.Response // make an app call txn with eval delta lsig := transactions.LogicSig{Logic: retOneProgram} // int 1 - program := logic.Program(lsig.Logic) - lhash := crypto.HashObj(&program) + lhash := logic.HashProgram(lsig.Logic) var sender basics.Address copy(sender[:], lhash[:]) stx := transactions.SignedTxn{ diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index 2f887d25a1..e2b52fc7c4 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -342,8 +342,7 @@ func testingenvWithBalances(t testing.TB, minMoneyAtStart, maxMoneyAtStart, numA genesis[poolAddr] = basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) - program := logic.Program(retOneProgram) - lhash := crypto.HashObj(&program) + lhash := logic.HashProgram(retOneProgram) var addr basics.Address copy(addr[:], lhash[:]) ad := basics_testing.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 100000 * uint64(proto.RewardsRateRefreshInterval)}) diff --git a/data/account/participation.go b/data/account/participation.go index 22130df6c1..9493f21f0d 100644 --- a/data/account/participation.go +++ b/data/account/participation.go @@ -80,8 +80,8 @@ func (id *ParticipationKeyIdentity) ToBeHashed() (protocol.HashID, []byte) { } // ID creates a ParticipationID hash from the identity file. -func (id ParticipationKeyIdentity) ID() ParticipationID { - return ParticipationID(crypto.HashObj(&id)) +func (id *ParticipationKeyIdentity) ID() ParticipationID { + return ParticipationID(crypto.HashObj(id)) } // ID computes a ParticipationID. diff --git a/data/account/participation_test.go b/data/account/participation_test.go index 4a933d72b7..ccf300122e 100644 --- a/data/account/participation_test.go +++ b/data/account/participation_test.go @@ -606,3 +606,13 @@ func BenchmarkParticipationSign(b *testing.B) { _ = part.Voting.Sign(ephID, msg) } } + +func BenchmarkID(b *testing.B) { + pki := ParticipationKeyIdentity{} + b.Run("existing", func(b *testing.B) { + b.ReportAllocs() // demonstrate this is a single alloc + for i := 0; i < b.N; i++ { + pki.ID() + } + }) +} diff --git a/data/bookkeeping/genesis_test.go b/data/bookkeeping/genesis_test.go index 72e94947f6..393e389229 100644 --- a/data/bookkeeping/genesis_test.go +++ b/data/bookkeeping/genesis_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -155,3 +156,30 @@ func TestGenesis_Balances(t *testing.T) { }) } } + +func (genesis Genesis) hashOld() crypto.Digest { + return hashObjOld(genesis) +} + +// hashObjOld computes a hash of a Hashable object and its type, doing so the +// "old way" to show it requires an extra allocation in benchmarks. +func hashObjOld(h crypto.Hashable) crypto.Digest { + return crypto.Hash(crypto.HashRep(h)) +} + +func BenchmarkGenesisHash(b *testing.B) { + b.ReportAllocs() + g := Genesis{} + b.Run("new", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + g.Hash() + } + }) + b.Run("old", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + g.hashOld() + } + }) +} diff --git a/data/transactions/logic/program.go b/data/transactions/logic/program.go index 4568ebe744..85195b9043 100644 --- a/data/transactions/logic/program.go +++ b/data/transactions/logic/program.go @@ -33,5 +33,5 @@ func (lsl Program) ToBeHashed() (protocol.HashID, []byte) { // This Digest can be used as an Address for a logic controlled account. func HashProgram(program []byte) crypto.Digest { pb := Program(program) - return crypto.HashObj(&pb) + return crypto.HashObj(pb) } From cd9719343b1e577ec2a941432e2df21fc896d438 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Fri, 17 Nov 2023 10:19:29 -0500 Subject: [PATCH 020/218] state proofs: add block hash to LightBlockHeader (#5663) --- config/consensus.go | 22 ++++++--- data/bookkeeping/lightBlockHeader.go | 20 ++++++++- data/bookkeeping/msgp_gen.go | 45 ++++++++++++++----- .../features/stateproofs/stateproofs_test.go | 1 + 4 files changed, 70 insertions(+), 18 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index 25b5dc7858..c8694f86e6 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -419,6 +419,11 @@ type ConsensusParams struct { // their account balances. StateProofExcludeTotalWeightWithRewards bool + // StateProofBlockHashInLightHeader specifies that the LightBlockHeader + // committed to by state proofs should contain the BlockHash of each + // block, instead of the seed. + StateProofBlockHashInLightHeader bool + // EnableAssetCloseAmount adds an extra field to the ApplyData. The field contains the amount of the remaining // asset that were sent to the close-to address. EnableAssetCloseAmount bool @@ -759,15 +764,20 @@ func LoadConfigurableConsensusProtocols(dataDirectory string) error { return err } if newConsensus != nil { - Consensus = newConsensus - // Set allocation limits - for _, p := range Consensus { - checkSetAllocBounds(p) - } + SetConfigurableConsensusProtocols(newConsensus) } return nil } +// SetConfigurableConsensusProtocols sets the configurable protocols. +func SetConfigurableConsensusProtocols(newConsensus ConsensusProtocols) { + Consensus = newConsensus + // Set allocation limits + for _, p := range Consensus { + checkSetAllocBounds(p) + } +} + // PreloadConfigurableConsensusProtocols loads the configurable protocols from the data directory // and merge it with a copy of the Consensus map. Then, it returns it to the caller. func PreloadConfigurableConsensusProtocols(dataDirectory string) (ConsensusProtocols, error) { @@ -1377,6 +1387,8 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here vFuture.EnableLogicSigCostPooling = true + vFuture.StateProofBlockHashInLightHeader = true + // Setting DynamicFilterTimeout in vFuture will impact e2e test performance // by reducing round time. Hence, it is commented out for now. // vFuture.DynamicFilterTimeout = true diff --git a/data/bookkeeping/lightBlockHeader.go b/data/bookkeeping/lightBlockHeader.go index ea283e0399..90edea3ba2 100644 --- a/data/bookkeeping/lightBlockHeader.go +++ b/data/bookkeeping/lightBlockHeader.go @@ -17,6 +17,7 @@ package bookkeeping import ( + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/committee" @@ -38,8 +39,15 @@ type LightBlockHeader struct { In addition, we make sure that the Seed (The unpredictable value) would be the first field that gets hashed (give it the lowest codec value in the LightBlockHeader struct) to mitigate a collision attack on the merkle damgard construction. + + The BlockHash serves a similar role, in that it also depends on the seed and introduces some + uncontrollable input. It is slightly weaker, in the sense that an adversary can influence + the BlockHash to some degree (e.g., by including specific transactions in the payset), but + it comes with the added benefit of allowing to authenticate the entire blockchain based on + the BlockHash value. */ Seed committee.Seed `codec:"0"` + BlockHash BlockHash `codec:"1"` Round basics.Round `codec:"r"` GenesisHash crypto.Digest `codec:"gh"` Sha256TxnCommitment crypto.GenericDigest `codec:"tc,allocbound=crypto.Sha256Size"` @@ -47,12 +55,20 @@ type LightBlockHeader struct { // ToLightBlockHeader creates returns a LightBlockHeader from a given block header func (bh *BlockHeader) ToLightBlockHeader() LightBlockHeader { - return LightBlockHeader{ - Seed: bh.Seed, + res := LightBlockHeader{ GenesisHash: bh.GenesisHash, Round: bh.Round, Sha256TxnCommitment: bh.Sha256Commitment[:], } + + proto := config.Consensus[bh.CurrentProtocol] + if proto.StateProofBlockHashInLightHeader { + res.BlockHash = bh.Hash() + } else { + res.Seed = bh.Seed + } + + return res } // ToBeHashed implements the crypto.Hashable interface diff --git a/data/bookkeeping/msgp_gen.go b/data/bookkeeping/msgp_gen.go index 7eda4de555..cb3a63ad2e 100644 --- a/data/bookkeeping/msgp_gen.go +++ b/data/bookkeeping/msgp_gen.go @@ -2663,23 +2663,27 @@ func GenesisAllocationMaxSize() (s int) { func (z *LightBlockHeader) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(4) - var zb0001Mask uint8 /* 5 bits */ + zb0001Len := uint32(5) + var zb0001Mask uint8 /* 6 bits */ if (*z).Seed.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x1 } + if (*z).BlockHash.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } if (*z).GenesisHash.MsgIsZero() { zb0001Len-- - zb0001Mask |= 0x4 + zb0001Mask |= 0x8 } if (*z).Round.MsgIsZero() { zb0001Len-- - zb0001Mask |= 0x8 + zb0001Mask |= 0x10 } if (*z).Sha256TxnCommitment.MsgIsZero() { zb0001Len-- - zb0001Mask |= 0x10 + zb0001Mask |= 0x20 } // variable map header, size zb0001Len o = append(o, 0x80|uint8(zb0001Len)) @@ -2689,17 +2693,22 @@ func (z *LightBlockHeader) MarshalMsg(b []byte) (o []byte) { o = append(o, 0xa1, 0x30) o = (*z).Seed.MarshalMsg(o) } - if (zb0001Mask & 0x4) == 0 { // if not empty + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "1" + o = append(o, 0xa1, 0x31) + o = (*z).BlockHash.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).GenesisHash.MarshalMsg(o) } - if (zb0001Mask & 0x8) == 0 { // if not empty + if (zb0001Mask & 0x10) == 0 { // if not empty // string "r" o = append(o, 0xa1, 0x72) o = (*z).Round.MarshalMsg(o) } - if (zb0001Mask & 0x10) == 0 { // if not empty + if (zb0001Mask & 0x20) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = (*z).Sha256TxnCommitment.MarshalMsg(o) @@ -2739,6 +2748,14 @@ func (z *LightBlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalSt return } } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).BlockHash.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "BlockHash") + return + } + } if zb0001 > 0 { zb0001-- bts, err = (*z).Round.UnmarshalMsgWithState(bts, st) @@ -2792,6 +2809,12 @@ func (z *LightBlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalSt err = msgp.WrapError(err, "Seed") return } + case "1": + bts, err = (*z).BlockHash.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "BlockHash") + return + } case "r": bts, err = (*z).Round.UnmarshalMsgWithState(bts, st) if err != nil { @@ -2833,18 +2856,18 @@ func (_ *LightBlockHeader) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *LightBlockHeader) Msgsize() (s int) { - s = 1 + 2 + (*z).Seed.Msgsize() + 2 + (*z).Round.Msgsize() + 3 + (*z).GenesisHash.Msgsize() + 3 + (*z).Sha256TxnCommitment.Msgsize() + s = 1 + 2 + (*z).Seed.Msgsize() + 2 + (*z).BlockHash.Msgsize() + 2 + (*z).Round.Msgsize() + 3 + (*z).GenesisHash.Msgsize() + 3 + (*z).Sha256TxnCommitment.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *LightBlockHeader) MsgIsZero() bool { - return ((*z).Seed.MsgIsZero()) && ((*z).Round.MsgIsZero()) && ((*z).GenesisHash.MsgIsZero()) && ((*z).Sha256TxnCommitment.MsgIsZero()) + return ((*z).Seed.MsgIsZero()) && ((*z).BlockHash.MsgIsZero()) && ((*z).Round.MsgIsZero()) && ((*z).GenesisHash.MsgIsZero()) && ((*z).Sha256TxnCommitment.MsgIsZero()) } // MaxSize returns a maximum valid message size for this message type func LightBlockHeaderMaxSize() (s int) { - s = 1 + 2 + committee.SeedMaxSize() + 2 + basics.RoundMaxSize() + 3 + crypto.DigestMaxSize() + 3 + crypto.GenericDigestMaxSize() + s = 1 + 2 + committee.SeedMaxSize() + 2 + BlockHashMaxSize() + 2 + basics.RoundMaxSize() + 3 + crypto.DigestMaxSize() + 3 + crypto.GenericDigestMaxSize() return } diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index b58669a6a6..ec0d4c2761 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -323,6 +323,7 @@ func TestStateProofMessageCommitmentVerification(t *testing.T) { consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") consensusParams := getDefaultStateProofConsensusParams() configurableConsensus[consensusVersion] = consensusParams + config.SetConfigurableConsensusProtocols(configurableConsensus) var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) From 89a193029870df9a041cf0ee908462b617d2134c Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Fri, 17 Nov 2023 10:47:13 -0500 Subject: [PATCH 021/218] config: add EnableGossipService option (#5832) --- config/localTemplate.go | 6 +- config/local_defaults.go | 3 +- installer/config.json.example | 3 +- network/wsNetwork.go | 9 +- network/wsNetwork_test.go | 33 ++++++ test/testdata/configs/config-v33.json | 140 ++++++++++++++++++++++++++ 6 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 test/testdata/configs/config-v33.json diff --git a/config/localTemplate.go b/config/localTemplate.go index 07a9bf5eb0..61c2381fa0 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -42,7 +42,7 @@ type Local struct { // Version tracks the current version of the defaults so we can migrate old -> new // This is specifically important whenever we decide to change the default value // for an existing parameter. This field tag must be updated any time we add a new version. - Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32"` + Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33"` // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. Relays (nodes with a valid NetAddress) are always Archival, regardless of this setting. This may change in the future. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only effects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. Archival bool `version[0]:"false"` @@ -375,6 +375,10 @@ type Local struct { // 0 means don't store any, -1 mean unlimited and positive number suggest the maximum number of most recent catchpoint files to store. CatchpointFileHistoryLength int `version[7]:"365"` + // EnableGossipService enables the gossip network HTTP websockets endpoint. The functionality of this depends on NetAddress, which must also be provided. + // This functionality is required for serving gossip traffic. + EnableGossipService bool `version[33]:"true"` + // EnableLedgerService enables the ledger serving service. The functionality of this depends on NetAddress, which must also be provided. // This functionality is required for the catchpoint catchup. EnableLedgerService bool `version[7]:"false"` diff --git a/config/local_defaults.go b/config/local_defaults.go index 06a26f2c1c..3df773a760 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -20,7 +20,7 @@ package config var defaultLocal = Local{ - Version: 32, + Version: 33, AccountUpdatesStatsInterval: 5000000000, AccountsRebuildSynchronousMode: 1, AgreementIncomingBundlesQueueLength: 15, @@ -70,6 +70,7 @@ var defaultLocal = Local{ EnableExperimentalAPI: false, EnableFollowMode: false, EnableGossipBlockService: true, + EnableGossipService: true, EnableIncomingMessageFilter: false, EnableLedgerService: false, EnableMetricReporting: false, diff --git a/installer/config.json.example b/installer/config.json.example index ce02380331..aa1cb71712 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -1,5 +1,5 @@ { - "Version": 32, + "Version": 33, "AccountUpdatesStatsInterval": 5000000000, "AccountsRebuildSynchronousMode": 1, "AgreementIncomingBundlesQueueLength": 15, @@ -49,6 +49,7 @@ "EnableExperimentalAPI": false, "EnableFollowMode": false, "EnableGossipBlockService": true, + "EnableGossipService": true, "EnableIncomingMessageFilter": false, "EnableLedgerService": false, "EnableMetricReporting": false, diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 05e7ba44ca..7f8b3046c9 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -591,7 +591,9 @@ func (wn *WebsocketNetwork) setup() { wn.upgrader.EnableCompression = false wn.lastPeerConnectionsSent = time.Now() wn.router = mux.NewRouter() - wn.router.Handle(GossipNetworkPath, wn) + if wn.config.EnableGossipService { + wn.router.Handle(GossipNetworkPath, wn) + } wn.requestsTracker = makeRequestsTracker(wn.router, wn.log, wn.config) if wn.config.EnableRequestLogger { wn.requestsLogger = makeRequestLogger(wn.requestsTracker, wn.log) @@ -1009,6 +1011,11 @@ func (wn *WebsocketNetwork) GetHTTPRequestConnection(request *http.Request) (con // ServerHTTP handles the gossip network functions over websockets func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *http.Request) { + if !wn.config.EnableGossipService { + response.WriteHeader(http.StatusNotFound) + return + } + trackedRequest := wn.requestsTracker.GetTrackedRequest(request) if wn.checkIncomingConnectionLimits(response, request, trackedRequest.remoteHost, trackedRequest.otherTelemetryGUID, trackedRequest.otherInstanceName) != http.StatusOK { diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 445ede3dc3..05e484843a 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -628,6 +628,10 @@ func TestWebsocketNetworkNoAddress(t *testing.T) { noAddressConfig := defaultConfig noAddressConfig.NetAddress = "" + // enable services even though NetAddress is not set (to assert they don't override NetAddress) + noAddressConfig.EnableGossipService = true + noAddressConfig.EnableBlockService = true + noAddressConfig.EnableLedgerService = true netB := makeTestWebsocketNodeWithConfig(t, noAddressConfig) netB.config.GossipFanout = 1 addrA, postListen := netA.Address() @@ -636,6 +640,12 @@ func TestWebsocketNetworkNoAddress(t *testing.T) { netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") + + // assert addrB is not listening + addrB, postListenB := netB.Address() + require.False(t, postListenB) + require.Empty(t, addrB) + counter := newMessageCounter(t, 2) counterDone := counter.done netB.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counter}}) @@ -656,6 +666,29 @@ func TestWebsocketNetworkNoAddress(t *testing.T) { } } +func TestWebsocketNetworkNoGossipService(t *testing.T) { + partitiontest.PartitionTest(t) + + config := defaultConfig + config.EnableGossipService = false + netA := makeTestWebsocketNodeWithConfig(t, config) + netA.Start() + defer netStop(t, netA, "A") + + // assert that the network was started and is listening + addrA, postListen := netA.Address() + require.True(t, postListen) + + // make HTTP request to gossip service and assert 404 + var resp *http.Response + require.Eventually(t, func() bool { + var err error + resp, err = http.Get(fmt.Sprintf("%s/v1/%s/gossip", addrA, genesisID)) + return err == nil + }, 2*time.Second, 100*time.Millisecond) + require.Equal(t, http.StatusNotFound, resp.StatusCode) +} + func lineNetwork(t *testing.T, numNodes int) (nodes []*WebsocketNetwork, counters []messageCounterHandler) { nodes = make([]*WebsocketNetwork, numNodes) counters = make([]messageCounterHandler, numNodes) diff --git a/test/testdata/configs/config-v33.json b/test/testdata/configs/config-v33.json new file mode 100644 index 0000000000..aa1cb71712 --- /dev/null +++ b/test/testdata/configs/config-v33.json @@ -0,0 +1,140 @@ +{ + "Version": 33, + "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, + "EnableGossipService": 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 +} From 5b62dc00727fa5bc61de94cb6870d3ad07446f91 Mon Sep 17 00:00:00 2001 From: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:03:25 -0500 Subject: [PATCH 022/218] cgo: Properly manage memory passing from cgo to go on Batch Verifiers (#5700) Co-authored-by: chris erway --- crypto/batchverifier.c | 20 ++++++++++ crypto/batchverifier.go | 81 ++++++++++++++++++----------------------- crypto/curve25519.go | 25 +++++++++++++ crypto/onetimesig.go | 15 +++++++- crypto/util.go | 8 ++++ crypto/util_test.go | 31 ++++++++++++++++ 6 files changed, 134 insertions(+), 46 deletions(-) create mode 100644 crypto/batchverifier.c diff --git a/crypto/batchverifier.c b/crypto/batchverifier.c new file mode 100644 index 0000000000..118542aa7b --- /dev/null +++ b/crypto/batchverifier.c @@ -0,0 +1,20 @@ +#include "sodium.h" +int ed25519_batch_wrapper(const unsigned char **messages2D, + const unsigned char **publicKeys2D, + const unsigned char **signatures2D, + const unsigned char *messages1D, + const unsigned long long *mlen, + const unsigned char *publicKeys1D, + const unsigned char *signatures1D, + size_t num, + int *valid) { + // fill 2-D arrays for messages, pks, sigs from provided 1-D arrays + unsigned long long mpos = 0; + for (size_t i = 0; i < num; i++) { + messages2D[i] = &messages1D[mpos]; + mpos += mlen[i]; + publicKeys2D[i] = &publicKeys1D[i*crypto_sign_ed25519_PUBLICKEYBYTES]; + signatures2D[i] = &signatures1D[i*crypto_sign_ed25519_BYTES]; + } + return crypto_sign_ed25519_open_batch(messages2D, mlen, publicKeys2D, signatures2D, num, valid); +} diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index 9c14771bac..af7a677ac3 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -30,15 +30,22 @@ package crypto // #cgo windows,amd64 CFLAGS: -I${SRCDIR}/libs/windows/amd64/include // #cgo windows,amd64 LDFLAGS: ${SRCDIR}/libs/windows/amd64/lib/libsodium.a // #include -// #include "sodium.h" // enum { // sizeofPtr = sizeof(void*), // sizeofULongLong = sizeof(unsigned long long), // }; +// int ed25519_batch_wrapper(const unsigned char **messages2D, +// const unsigned char **publicKeys2D, +// const unsigned char **signatures2D, +// const unsigned char *messages1D, +// const unsigned long long *mlen, +// const unsigned char *publicKeys1D, +// const unsigned char *signatures1D, +// size_t num, +// int *valid_p); import "C" import ( "errors" - "runtime" "unsafe" ) @@ -120,14 +127,21 @@ func (b *BatchVerifier) Verify() error { // if some signatures are invalid, true will be set in failed at the corresponding indexes, and // ErrBatchVerificationFailed for err func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) { - if b.GetNumberOfEnqueuedSignatures() == 0 { + if len(b.messages) == 0 { return nil, nil } - var messages = make([][]byte, b.GetNumberOfEnqueuedSignatures()) + + const estimatedMessageSize = 64 + msgLengths := make([]uint64, 0, len(b.messages)) + var messages = make([]byte, 0, len(b.messages)*estimatedMessageSize) + + lenWas := 0 for i := range b.messages { - messages[i] = HashRep(b.messages[i]) + messages = HashRepToBuff(b.messages[i], messages) + msgLengths = append(msgLengths, uint64(len(messages)-lenWas)) + lenWas = len(messages) } - allValid, failed := batchVerificationImpl(messages, b.publicKeys, b.signatures) + allValid, failed := batchVerificationImpl(messages, msgLengths, b.publicKeys, b.signatures) if allValid { return failed, nil } @@ -137,50 +151,27 @@ func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) { // batchVerificationImpl invokes the ed25519 batch verification algorithm. // it returns true if all the signatures were authentically signed by the owners // otherwise, returns false, and sets the indexes of the failed sigs in failed -func batchVerificationImpl(messages [][]byte, publicKeys []SignatureVerifier, signatures []Signature) (allSigsValid bool, failed []bool) { - - numberOfSignatures := len(messages) - - messagesAllocation := C.malloc(C.size_t(C.sizeofPtr * numberOfSignatures)) - messagesLenAllocation := C.malloc(C.size_t(C.sizeofULongLong * numberOfSignatures)) - publicKeysAllocation := C.malloc(C.size_t(C.sizeofPtr * numberOfSignatures)) - signaturesAllocation := C.malloc(C.size_t(C.sizeofPtr * numberOfSignatures)) - valid := C.malloc(C.size_t(C.sizeof_int * numberOfSignatures)) - - defer func() { - // release staging memory - C.free(messagesAllocation) - C.free(messagesLenAllocation) - C.free(publicKeysAllocation) - C.free(signaturesAllocation) - C.free(valid) - }() - - // load all the data pointers into the array pointers. - for i := 0; i < numberOfSignatures; i++ { - *(*uintptr)(unsafe.Pointer(uintptr(messagesAllocation) + uintptr(i*C.sizeofPtr))) = uintptr(unsafe.Pointer(&messages[i][0])) - *(*C.ulonglong)(unsafe.Pointer(uintptr(messagesLenAllocation) + uintptr(i*C.sizeofULongLong))) = C.ulonglong(len(messages[i])) - *(*uintptr)(unsafe.Pointer(uintptr(publicKeysAllocation) + uintptr(i*C.sizeofPtr))) = uintptr(unsafe.Pointer(&publicKeys[i][0])) - *(*uintptr)(unsafe.Pointer(uintptr(signaturesAllocation) + uintptr(i*C.sizeofPtr))) = uintptr(unsafe.Pointer(&signatures[i][0])) - } +func batchVerificationImpl(messages []byte, msgLengths []uint64, publicKeys []SignatureVerifier, signatures []Signature) (allSigsValid bool, failed []bool) { + + numberOfSignatures := len(msgLengths) + valid := make([]C.int, numberOfSignatures) + messages2D := make([]*C.uchar, numberOfSignatures) + publicKeys2D := make([]*C.uchar, numberOfSignatures) + signatures2D := make([]*C.uchar, numberOfSignatures) // call the batch verifier - allValid := C.crypto_sign_ed25519_open_batch( - (**C.uchar)(unsafe.Pointer(messagesAllocation)), - (*C.ulonglong)(unsafe.Pointer(messagesLenAllocation)), - (**C.uchar)(unsafe.Pointer(publicKeysAllocation)), - (**C.uchar)(unsafe.Pointer(signaturesAllocation)), - C.size_t(len(messages)), - (*C.int)(unsafe.Pointer(valid))) - - runtime.KeepAlive(messages) - runtime.KeepAlive(publicKeys) - runtime.KeepAlive(signatures) + allValid := C.ed25519_batch_wrapper( + &messages2D[0], &publicKeys2D[0], &signatures2D[0], + (*C.uchar)(&messages[0]), + (*C.ulonglong)(&msgLengths[0]), + (*C.uchar)(&publicKeys[0][0]), + (*C.uchar)(&signatures[0][0]), + C.size_t(numberOfSignatures), + (*C.int)(&valid[0])) failed = make([]bool, numberOfSignatures) for i := 0; i < numberOfSignatures; i++ { - cint := *(*C.int)(unsafe.Pointer(uintptr(valid) + uintptr(i*C.sizeof_int))) - failed[i] = (cint == 0) + failed[i] = (valid[i] == 0) } return allValid == 0, failed } diff --git a/crypto/curve25519.go b/crypto/curve25519.go index 58950a3de3..a8637399d7 100644 --- a/crypto/curve25519.go +++ b/crypto/curve25519.go @@ -35,6 +35,7 @@ import "C" import ( "fmt" + "unsafe" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/util/metrics" @@ -64,6 +65,30 @@ func init() { _ = [C.crypto_sign_ed25519_PUBLICKEYBYTES]byte(ed25519PublicKey{}) _ = [C.crypto_sign_ed25519_SECRETKEYBYTES]byte(ed25519PrivateKey{}) _ = [C.crypto_sign_ed25519_SEEDBYTES]byte(ed25519Seed{}) + + // Check that this platform makes slices []Signature and []SignatureVerifier that use a backing + // array of contiguously allocated 64- and 32-byte segments, respectively, with no padding. + // These slice's backing arrays are passed to C.ed25519_batch_wrapper. In practice, this check + // should always succeed, but to be careful we can double-check, since the Go specification does + // not explicitly define platform-specific alignment sizes and slice allocation behavior. + length := 1024 + sigs := make([]Signature, length) // same as [][64]byte + pks := make([]SignatureVerifier, length) // same as [][32]byte + + for i := 1; i < length; i++ { + if uintptr(unsafe.Pointer(&sigs[i]))-uintptr(unsafe.Pointer(&sigs[0])) != uintptr(i)*C.crypto_sign_ed25519_BYTES { + panic("Unexpected alignment for a slice of signatures") + } + if uintptr(unsafe.Pointer(&pks[i]))-uintptr(unsafe.Pointer(&pks[0])) != uintptr(i)*C.crypto_sign_ed25519_PUBLICKEYBYTES { + panic("Unexpected alignment for a slice of public keys") + } + } + if uintptr(unsafe.Pointer(&sigs[length-1]))-uintptr(unsafe.Pointer(&sigs[0])) != uintptr(length-1)*C.crypto_sign_ed25519_BYTES { + panic("Unexpected total size for a backing array of signatures") + } + if uintptr(unsafe.Pointer(&pks[length-1]))-uintptr(unsafe.Pointer(&pks[0])) != uintptr(length-1)*C.crypto_sign_ed25519_PUBLICKEYBYTES { + panic("Unexpected total size for a backing array of public keys") + } } // A Seed holds the entropy needed to generate cryptographic keys. diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index 1de4854967..a2db211a79 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -319,8 +319,21 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message Batch: id.Batch, } + // serialize encoded batchID, offsetID, message into a continuous memory buffer with the layout + // hashRep(batchID)... hashRep(offsetID)... hashRep(message)... + const estimatedSize = 256 + messageBuffer := make([]byte, 0, estimatedSize) + + messageBuffer = HashRepToBuff(batchID, messageBuffer) + batchIDLen := uint64(len(messageBuffer)) + messageBuffer = HashRepToBuff(offsetID, messageBuffer) + offsetIDLen := uint64(len(messageBuffer)) - batchIDLen + messageBuffer = HashRepToBuff(message, messageBuffer) + messageLen := uint64(len(messageBuffer)) - offsetIDLen - batchIDLen + msgLengths := []uint64{batchIDLen, offsetIDLen, messageLen} allValid, _ := batchVerificationImpl( - [][]byte{HashRep(batchID), HashRep(offsetID), HashRep(message)}, + messageBuffer, + msgLengths, []PublicKey{PublicKey(v), PublicKey(batchID.SubKeyPK), PublicKey(offsetID.SubKeyPK)}, []Signature{Signature(sig.PK2Sig), Signature(sig.PK1Sig), Signature(sig.Sig)}, ) diff --git a/crypto/util.go b/crypto/util.go index cc567f3998..078d52c0cd 100644 --- a/crypto/util.go +++ b/crypto/util.go @@ -40,6 +40,14 @@ func HashRep[H Hashable](h H) []byte { return append([]byte(hashid), data...) } +// HashRepToBuff appends the correct hashid before the message to be hashed into the provided buffer +func HashRepToBuff(h Hashable, buffer []byte) []byte { + hashid, data := h.ToBeHashed() + buffer = append(buffer, hashid...) + buffer = append(buffer, data...) + return buffer +} + // DigestSize is the number of bytes in the preferred hash Digest used here. const DigestSize = sha512.Size256 diff --git a/crypto/util_test.go b/crypto/util_test.go index 667da0bcd0..2e0828bcce 100644 --- a/crypto/util_test.go +++ b/crypto/util_test.go @@ -17,8 +17,10 @@ package crypto import ( + "fmt" "testing" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) @@ -46,3 +48,32 @@ func TestDigest_IsZero(t *testing.T) { require.NotZero(t, d2) } + +type testToBeHashed struct { + i int +} + +func (tbh *testToBeHashed) ToBeHashed() (protocol.HashID, []byte) { + data := make([]byte, tbh.i) + for x := 0; x < tbh.i; x++ { + data[x] = byte(tbh.i) + } + return protocol.HashID(fmt.Sprintf("ID%d", tbh.i)), data +} + +func TestHashRepToBuff(t *testing.T) { + partitiontest.PartitionTest(t) + values := []int{32, 64, 512, 1024} + buffer := make([]byte, 0, 128) + for _, val := range values { + tbh := &testToBeHashed{i: val} + buffer = HashRepToBuff(tbh, buffer) + } + pos := 0 + for _, val := range values { + tbh := &testToBeHashed{i: val} + data := HashRep(tbh) + require.Equal(t, data, buffer[pos:pos+len(data)]) + pos = pos + len(data) + } +} From fcc686dd7e682002c5becb35421fd673d1a2dd6e Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Sat, 18 Nov 2023 09:52:02 -0500 Subject: [PATCH 023/218] tests: fix state proof e2e test failures (#5839) --- config/consensus.go | 4 +++- test/e2e-go/features/stateproofs/stateproofs_test.go | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index c8694f86e6..a2f28b97d5 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -770,12 +770,14 @@ func LoadConfigurableConsensusProtocols(dataDirectory string) error { } // SetConfigurableConsensusProtocols sets the configurable protocols. -func SetConfigurableConsensusProtocols(newConsensus ConsensusProtocols) { +func SetConfigurableConsensusProtocols(newConsensus ConsensusProtocols) ConsensusProtocols { + oldConsensus := Consensus Consensus = newConsensus // Set allocation limits for _, p := range Consensus { checkSetAllocBounds(p) } + return oldConsensus } // PreloadConfigurableConsensusProtocols loads the configurable protocols from the data directory diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index ec0d4c2761..4631ecd6e9 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -323,7 +323,8 @@ func TestStateProofMessageCommitmentVerification(t *testing.T) { consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") consensusParams := getDefaultStateProofConsensusParams() configurableConsensus[consensusVersion] = consensusParams - config.SetConfigurableConsensusProtocols(configurableConsensus) + oldConsensus := config.SetConfigurableConsensusProtocols(configurableConsensus) + defer config.SetConfigurableConsensusProtocols(oldConsensus) var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) From 3abe5c87afa05022d80093d7f290714c131763e2 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 20 Nov 2023 09:01:19 -0500 Subject: [PATCH 024/218] chore: Cut out some useless allocations when computing payset commitments (#5840) --- crypto/hashes.go | 2 +- crypto/merklearray/layer.go | 6 +++--- crypto/merklearray/merkle_test.go | 5 ++++- crypto/merklearray/partial.go | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crypto/hashes.go b/crypto/hashes.go index 8933717e4f..04db757f39 100644 --- a/crypto/hashes.go +++ b/crypto/hashes.go @@ -118,7 +118,7 @@ func (z *HashFactory) Validate() error { } // GenericHashObj Makes it easier to sum using hash interface and Hashable interface -func GenericHashObj(hsh hash.Hash, h Hashable) []byte { +func GenericHashObj[H Hashable](hsh hash.Hash, h H) []byte { rep := HashRep(h) return hashBytes(hsh, rep) } diff --git a/crypto/merklearray/layer.go b/crypto/merklearray/layer.go index 5018ae074b..88eed6ffe5 100644 --- a/crypto/merklearray/layer.go +++ b/crypto/merklearray/layer.go @@ -37,14 +37,14 @@ type pair struct { hashDigestSize int } -func (p *pair) ToBeHashed() (protocol.HashID, []byte) { +func (p pair) ToBeHashed() (protocol.HashID, []byte) { // hashing of internal node will always be fixed length. // If one of the children is missing we use [0...0]. // The size of the slice is based on the relevant hash function output size buf := make([]byte, 2*p.hashDigestSize) copy(buf[:], p.l[:]) copy(buf[len(p.l):], p.r[:]) - return protocol.MerkleArrayNode, buf[:] + return protocol.MerkleArrayNode, buf } func upWorker(ws *workerState, in Layer, out Layer, h hash.Hash) { @@ -69,7 +69,7 @@ func upWorker(ws *workerState, in Layer, out Layer, h hash.Hash) { p.r = in[i+1] } - out[i/2] = crypto.GenericHashObj(h, &p) + out[i/2] = crypto.GenericHashObj(h, p) } batchSize += 2 diff --git a/crypto/merklearray/merkle_test.go b/crypto/merklearray/merkle_test.go index 9a1a5c0fd7..0d392dcef6 100644 --- a/crypto/merklearray/merkle_test.go +++ b/crypto/merklearray/merkle_test.go @@ -1172,12 +1172,13 @@ func merkleCommitBench(b *testing.B, hashType crypto.HashType) { msg := make(TestBuf, sz) crypto.RandBytes(msg[:]) - for cnt := 10; cnt <= 10000000; cnt *= 10 { + for cnt := 10; cnt <= 100000; cnt *= 10 { var a TestRepeatingArray a.item = msg a.count = uint64(cnt) b.Run(fmt.Sprintf("Item%d/Count%d", sz, cnt), func(b *testing.B) { + b.ReportAllocs() for i := 0; i < b.N; i++ { tree, err := Build(a, crypto.HashFactory{HashType: hashType}) require.NoError(b, err) @@ -1205,6 +1206,7 @@ func benchmarkMerkleProve1M(b *testing.B, hashType crypto.HashType) { require.NoError(b, err) b.ResetTimer() + b.ReportAllocs() for i := uint64(0); i < uint64(b.N); i++ { _, err := tree.Prove([]uint64{i % a.count}) @@ -1238,6 +1240,7 @@ func benchmarkMerkleVerify1M(b *testing.B, hashType crypto.HashType) { } b.ResetTimer() + b.ReportAllocs() for i := uint64(0); i < uint64(b.N); i++ { err := Verify(root, map[uint64]crypto.Hashable{i % a.count: msg}, proofs[i]) diff --git a/crypto/merklearray/partial.go b/crypto/merklearray/partial.go index 4baf777f31..b1aa07c526 100644 --- a/crypto/merklearray/partial.go +++ b/crypto/merklearray/partial.go @@ -118,7 +118,7 @@ func (pl partialLayer) up(s *siblings, l uint64, doHash bool, hsh hash.Hash) (pa p.l = siblingHash p.r = posHash } - nextLayerHash = crypto.GenericHashObj(hsh, &p) + nextLayerHash = crypto.GenericHashObj(hsh, p) } res = append(res, layerItem{ From cf428379d5047fb6bfd6668386bd02708b6c2e81 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 28 Nov 2023 12:25:05 -0500 Subject: [PATCH 025/218] catchup: Fix empty cert if ledger already has a block (#5846) --- catchup/catchpointService.go | 11 +++-- catchup/catchpointService_test.go | 70 +++++++++++++++++++++++++++++-- ledger/catchupaccessor.go | 2 +- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index a5175aff4c..a0a22c5e33 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -373,8 +373,9 @@ func (cs *CatchpointCatchupService) processStageLatestBlockDownload() (err error var blk *bookkeeping.Block var cert *agreement.Certificate // check to see if the current ledger might have this block. If so, we should try this first instead of downloading anything. - if ledgerBlock, err := cs.ledger.Block(blockRound); err == nil { + if ledgerBlock, ledgerCert, err0 := cs.ledger.BlockCert(blockRound); err0 == nil { blk = &ledgerBlock + cert = &ledgerCert } var protoParams config.ConsensusParams var ok bool @@ -551,15 +552,17 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { } blk = nil + cert = nil // check to see if the current ledger might have this block. If so, we should try this first instead of downloading anything. - if ledgerBlock, err := cs.ledger.Block(topBlock.Round() - basics.Round(blocksFetched)); err == nil { + if ledgerBlock, ledgerCert, err0 := cs.ledger.BlockCert(topBlock.Round() - basics.Round(blocksFetched)); err0 == nil { blk = &ledgerBlock + cert = &ledgerCert } else { - switch err.(type) { + switch err0.(type) { case ledgercore.ErrNoEntry: // this is expected, ignore this one. default: - cs.log.Warnf("processStageBlocksDownload encountered the following error when attempting to retrieve the block for round %d : %v", topBlock.Round()-basics.Round(blocksFetched), err) + cs.log.Warnf("processStageBlocksDownload encountered the following error when attempting to retrieve the block for round %d : %v", topBlock.Round()-basics.Round(blocksFetched), err0) } } diff --git a/catchup/catchpointService_test.go b/catchup/catchpointService_test.go index 48cea110d7..34f1adf0fd 100644 --- a/catchup/catchpointService_test.go +++ b/catchup/catchpointService_test.go @@ -22,12 +22,14 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/components/mocks" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -35,7 +37,7 @@ import ( type catchpointCatchupLedger struct { } -func (l *catchpointCatchupLedger) Block(rnd basics.Round) (blk bookkeeping.Block, err error) { +func (l *catchpointCatchupLedger) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreement.Certificate, err error) { blk = bookkeeping.Block{ BlockHeader: bookkeeping.BlockHeader{ UpgradeState: bookkeeping.UpgradeState{ @@ -43,13 +45,14 @@ func (l *catchpointCatchupLedger) Block(rnd basics.Round) (blk bookkeeping.Block }, }, } + cert = agreement.Certificate{} commitments, err := blk.PaysetCommit() if err != nil { - return blk, err + return blk, cert, err } blk.TxnCommitments = commitments - return blk, nil + return blk, cert, nil } func (l *catchpointCatchupLedger) GenesisHash() (d crypto.Digest) { @@ -95,3 +98,64 @@ func TestCatchpointServicePeerRank(t *testing.T) { err := cs.processStageLatestBlockDownload() require.NoError(t, err) } + +type catchpointAccessorMock struct { + mocks.MockCatchpointCatchupAccessor + t *testing.T + topBlk bookkeeping.Block +} + +func (m *catchpointAccessorMock) EnsureFirstBlock(ctx context.Context) (blk bookkeeping.Block, err error) { + return m.topBlk, nil +} + +func (m *catchpointAccessorMock) StoreBlock(ctx context.Context, blk *bookkeeping.Block, cert *agreement.Certificate) (err error) { + require.NotNil(m.t, blk) + require.NotNil(m.t, cert) + return nil +} + +type catchpointCatchupLedger2 struct { + catchpointCatchupLedger + blk bookkeeping.Block +} + +func (l *catchpointCatchupLedger2) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreement.Certificate, err error) { + return l.blk, agreement.Certificate{}, nil +} + +// TestProcessStageBlocksDownloadNilCert ensures StoreBlock does not receive a nil certificate when ledger has already had a block. +// It uses two mocks catchpointAccessorMock and catchpointCatchupLedger2 and pre-crafted blocks to make a single iteration of processStageBlocksDownload. +func TestProcessStageBlocksDownloadNilCert(t *testing.T) { + partitiontest.PartitionTest(t) + + var err error + blk1 := bookkeeping.Block{ + BlockHeader: bookkeeping.BlockHeader{ + Round: 1, + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: protocol.ConsensusCurrentVersion, + }, + }, + } + blk1.TxnCommitments, err = blk1.PaysetCommit() + require.NoError(t, err) + + blk2 := blk1 + blk2.BlockHeader.Round = 2 + blk2.BlockHeader.Branch = blk1.Hash() + blk2.TxnCommitments, err = blk2.PaysetCommit() + require.NoError(t, err) + + ctx, cf := context.WithCancel(context.Background()) + cs := CatchpointCatchupService{ + ctx: ctx, + cancelCtxFunc: cf, + ledgerAccessor: &catchpointAccessorMock{topBlk: blk2, t: t}, + ledger: &catchpointCatchupLedger2{blk: blk1}, + log: logging.TestingLog(t), + } + + err = cs.processStageBlocksDownload() + require.NoError(t, err) +} diff --git a/ledger/catchupaccessor.go b/ledger/catchupaccessor.go index 64ada07a08..3661c60057 100644 --- a/ledger/catchupaccessor.go +++ b/ledger/catchupaccessor.go @@ -238,7 +238,7 @@ const ( // CatchupAccessorClientLedger represents ledger interface needed for catchpoint accessor clients type CatchupAccessorClientLedger interface { - Block(rnd basics.Round) (blk bookkeeping.Block, err error) + BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreement.Certificate, err error) GenesisHash() crypto.Digest BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) Latest() (rnd basics.Round) From 89254b4d55bb294826f143b2e81ac657a8ae5d2c Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Wed, 29 Nov 2023 15:50:48 +0000 Subject: [PATCH 026/218] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildnumber.dat b/buildnumber.dat index 573541ac97..d00491fd7e 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -0 +1 From ae133b81212c066769aab9c6ea45185aea791dce Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 30 Nov 2023 16:52:44 -0500 Subject: [PATCH 027/218] docs: participation key lifecycle. (#5847) --- docs/participation_key_lifecycle.md | 210 ++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 docs/participation_key_lifecycle.md diff --git a/docs/participation_key_lifecycle.md b/docs/participation_key_lifecycle.md new file mode 100644 index 0000000000..6609d5f4bc --- /dev/null +++ b/docs/participation_key_lifecycle.md @@ -0,0 +1,210 @@ +# Participation Key Lifecycle + +This document goes into technical details about the lifecycle of participation +keys. Before getting into that let's briefly discuss the purpose of these keys. + +## Overview +Participation keys are used to participate in the consensus protocol. Aside +from the registration process, they are completely decoupled from the account +secret key. + +Each set of participation keys includes voting keys for each and every round +for their validity range, in addition to state proof secrets for each and every +state proof round (a state proof is not created every round). Because there +are separate keys for each action we sometimes refer to the voting keys as +**OneTimeKeys**. After each key is used, the secret is permanently deleted from +the node. This is a security property that eliminates the possibility of votes +being changed in the event a node becomes compromised at a later date. This +helps ensure finality even if a serious security breach were to occur. + +Aside from the **Registration** event, keys are completely decoupled from the +account secrets. This is another security property. A securely managed account +need only be exposed when signing the key registration transaction. For this +reason tools like **algokey** are available for fully offline transaction +signing. + +Given a set of participation keys, and a signed key registration transaction, +an algod node can be configured to participate in the consensus protocol. +For each round a single-use key is used to verify the authenticity of messages. + +## Key Creation + +Keys can be created with **goal** or **algokey**. There are a number of +shortcuts and helpers, especially in **goal**, which won't be covered here. + +Required parameters are as follows: +* **First**: the first round the keys will be valid for. +* **Last**: the last round the keys will be valid for. +* **Parent**: The public address of the account that will register the keys. + +The parent is stored along with the keys for convenience and error checking. +It is not part of the cryptography. It can technically be set later, but must +be set before the keys can be installed or registered. + +There is one optional parameter: +* **Dilution**: Configure the multi-level key storage. + +Internally, keys are stored as a two-level hierarchy of ed25519 keys. + +```mermaid +flowchart TD + root["onetime signature container"] --> p0["Batch Signer 1"] + root --> p1["Batch Signer 2"] + root --> pDot["..."] + root --> pN["Batch Signer N"] + p0 --> v0["Batch 1 Key 1"] + p0 --> v1["Batch 1 Key 2"] + p0 --> vDot["..."] + p0 --> vN["Batch 1 Key N"] +``` + +The top-level array of keys are used to generate the actual voting keys. +This allows them to be significantly smaller, because voting keys are not +generated until they are required. The **Dilution** parameter defines how many +voting keys are generated for each top-level key. This is done for space +efficiency. It is optional because **sqrt(Last - First)** gives us the most +space efficient value. + +Using **algokey** a set of keys can be generated with the command: +``` +algokey part generate --first 35000000 --last 36000000 --parent --keyfile keys.db +``` + +This creates a SQLite DB file named **keys.db**. The schema is pretty basic, +consisting of BLOBs for voting keys. State proof keys are also included and are +a bit more involved in their storage pattern. + +Similar functionality is built into **goal** along with convenience methods to: +* Generate and install. +* Generate, install and register. +* Lookup account, regenerate, install and register. +* Maybe others, we got carried away. + +## Key Installation + +The node can only use keys which have been installed, so files like **keys.db** +generated above must be provided to the node. Technically, it's possible for +an account to register a set of keys which are not installed, but this is very +bad. It means the stake calculations are accounting for an account which does +not have a properly configured node. + +In older versions of **algod**, keys were installed by dropping the database +file into the data directory. This caused frequent errors with many deployments +because the data directory is often owned by a restricted user and copying the +files around could easily lead to permission errors. + +The current version of **algod** supports a series of endpoints on the Admin API +to install and manage keys. One of them is a POST for installation: + +``` +POST /v2/participation/ +``` + +For example, install the keys generated above to a node located at **localhost:1234** with the following: +``` +curl -X POST --data-binary @keys.db -H "Authorization: Bearer admin-token-here" "localhost:1234/v2/participation" +``` + +**goal** provides a convenience function for this operation: +``` +goal account installpartkey --partkey keys.db --delete-input -d /path/to/data-dir +``` + +Keys should only be installed on a single node. In the event that multiple +nodes have the same set of keys, multiple votes could be cast for a single +account. This could lead to "equivocation" votes, where multiple conflicting +votes are cast for the same account causing both to be ignored. + +## Key Storage + +Once installed keys are stored in the **Participation Registry**. This is a +service that wraps a SQLite file for storage. Once installed, keys are assigned +an ID, which is referred to as **** below. The ID is a hash +built from parts of the participation key metadata. There are additional Admin +API endpoints available to manage the registry: + +``` +DELETE /v2/participation/ +GET /v2/participation/ +GET /v2/participation/ +``` + +## Key Registration + +After installing a key, the account is still considered offline. The network +does not know about the local keys and will not accept votes until the keys are +registered with the network. This is done using a special key registration +transaction. + +Like account creation, there are utilities in both **goal** and **algokey**. +For this document we'll use **algokey**. Because it is designed to be used offline +you need to provide the current round. + +It has the following arguments: +* **network**: One of devnet, betanet, testnet or mainnet. +* **firstvalid**: The first round where this transaction may be submitted. +* **keyfile**: The keyfile generated earlier. This is used to source most parameters. +* **outputFile**: The unsigned keyreg transaction. + +Here is an example call that creates **keyreg.txn**, an unsigned keyreg transaction: +``` +algokey part keyreg --network mainnet --firstvalid 31998000 --keyfile keys.db -o keyreg.txn +``` + +The unsigned transaction can also be signed with **algokey**: +``` +algokey sign -t keyreg.txn -o keyreg.stxn -m “[enter your account’s 25 word private key delimited by spaces]” +``` + +Now copy the signed **keyreg.stxn** file to an node and submit it as usual. +It does not need to be the same node which was used to install the keys. +``` +goal clerk rawsend -f keyreg.stxn -d /path/to/data-dir +``` + +The node monitors blocks for key registrations and updates the **Participation Registry**. + +## Voting + +The node asks the registry for keys on each round and uses them as needed. At +the end of the round it tells the registry to delete them. + +## Participation Registry + +The participation registry is optimized in several ways to avoid slowing down +the consensus protocol. + +Reads are optimized by caching all keys on startup. + +Writes are optimized by putting disk IO in a separate thread. Operations like +deleting old keys (and installing, updating, etc) are performed asynchronously. +The cache is manually updated when async operations are initiated to ensure it +always represents the current state. + +# Appendix 1: Key Registration Delay and Overlapping keys + +When a key is registered, there is a delay of 320 rounds before it can be used. +The number 320 is defined by the **balanceRound** function, which derives it +from the **SeedRefreshInterval** and **SeedLookback** consensus settings. +This delay is intended to circumvent some specific attacks related to +registering new voting keys at a high frequency. See the research papers for +those details, here we'll focus on some implications of this property. + +* When an account is brought online for the first time, it doesn't vote until + 320 rounds after it was registered. +* When an account renews its voting keys by installing a new set of keys and + registering them, there is a 320 round window where the old keys are still + used. During this window you must not remove first set of keys from the node + or else your account will not vote properly. + +# Appendix 2: On-chain Storage + +When a key registration transaction is evaluated, public keys required to +verify votes from that account are written to the account record. This is the +only on-chain component for voting keys. + +Each participating node will accumulate votes and write them to a +**Certificate** which serves to validate the block. The certificate would be +validated using the public keys stored in each account. Because there are many +nodes accumulating votes at the same time, it is possible to have multiple +correct but different certificates validating the same block. From ed278b874061373243aa4842b459e395c714c80b Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 4 Dec 2023 15:27:29 -0500 Subject: [PATCH 028/218] AVM: Add box_splice and box_resize opcodes (#5750) --- data/transactions/logic/README.md | 10 +- data/transactions/logic/README_in.md | 6 + data/transactions/logic/TEAL_opcodes_v10.md | 20 +++- data/transactions/logic/TEAL_opcodes_v8.md | 2 +- data/transactions/logic/TEAL_opcodes_v9.md | 2 +- data/transactions/logic/assembler_test.go | 15 ++- data/transactions/logic/box.go | 110 +++++++++++++++++++ data/transactions/logic/box_test.go | 100 ++++++++++++++++- data/transactions/logic/doc.go | 6 +- data/transactions/logic/eval.go | 2 +- data/transactions/logic/langspec_v10.json | 35 +++++- data/transactions/logic/langspec_v8.json | 2 +- data/transactions/logic/langspec_v9.json | 2 +- data/transactions/logic/opcodeExplain.go | 12 ++ data/transactions/logic/opcodes.go | 3 + data/transactions/logic/teal.tmLanguage.json | 2 +- 16 files changed, 311 insertions(+), 18 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 3ba9037fc1..da4fdefac9 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -751,6 +751,12 @@ Account fields used in the `acct_params_get` opcode. ### Box Access +Box opcodes that create, delete, or resize boxes affect the minimum +balance requirement of the calling application's account. The change +is immediate, and can be observed after exection by using +`min_balance`. If the account does not possess the new minimum +balance, the opcode fails. + All box related opcodes fail immediately if used in a ClearStateProgram. This behavior is meant to discourage Smart Contract authors from depending upon the availability of boxes in a ClearState @@ -763,13 +769,15 @@ are sure to be _available_. | Opcode | Description | | - | -- | -| `box_create` | create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 | +| `box_create` | create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 | | `box_extract` | read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. | | `box_replace` | write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size. | +| `box_splice` | set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C. | | `box_del` | delete box named A if it exists. Return 1 if A existed, 0 otherwise | | `box_len` | X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0. | | `box_get` | X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0. | | `box_put` | replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist | +| `box_resize` | change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768. | ### Inner Transactions diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index e98d6c2441..31f8fb05be 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -406,6 +406,12 @@ Account fields used in the `acct_params_get` opcode. ### Box Access +Box opcodes that create, delete, or resize boxes affect the minimum +balance requirement of the calling application's account. The change +is immediate, and can be observed after exection by using +`min_balance`. If the account does not possess the new minimum +balance, the opcode fails. + All box related opcodes fail immediately if used in a ClearStateProgram. This behavior is meant to discourage Smart Contract authors from depending upon the availability of boxes in a ClearState diff --git a/data/transactions/logic/TEAL_opcodes_v10.md b/data/transactions/logic/TEAL_opcodes_v10.md index 4ec00a52bb..7df81c33a8 100644 --- a/data/transactions/logic/TEAL_opcodes_v10.md +++ b/data/transactions/logic/TEAL_opcodes_v10.md @@ -1487,7 +1487,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with - Bytecode: 0xb9 - Stack: ..., A: boxName, B: uint64 → ..., bool -- create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 +- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 - Availability: v8 - Mode: Application @@ -1641,6 +1641,24 @@ Fields | 1 | BlkTimestamp | uint64 | | +## box_splice + +- Bytecode: 0xd2 +- Stack: ..., A: boxName, B: uint64, C: uint64, D: []byte → ... +- set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C. +- Availability: v10 +- Mode: Application + +Boxes are of constant length. If C < len(D), then len(D)-C bytes will be removed from the end. If C > len(D), zero bytes will be appended to the end to reach the box length. + +## box_resize + +- Bytecode: 0xd3 +- Stack: ..., A: boxName, B: uint64 → ... +- change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768. +- Availability: v10 +- Mode: Application + ## ec_add - Syntax: `ec_add G` ∋ G: [EC](#field-group-ec) diff --git a/data/transactions/logic/TEAL_opcodes_v8.md b/data/transactions/logic/TEAL_opcodes_v8.md index b8efb37fa3..71d756a1dc 100644 --- a/data/transactions/logic/TEAL_opcodes_v8.md +++ b/data/transactions/logic/TEAL_opcodes_v8.md @@ -1485,7 +1485,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with - Bytecode: 0xb9 - Stack: ..., A: boxName, B: uint64 → ..., bool -- create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 +- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 - Availability: v8 - Mode: Application diff --git a/data/transactions/logic/TEAL_opcodes_v9.md b/data/transactions/logic/TEAL_opcodes_v9.md index 54f053686f..f2ff330591 100644 --- a/data/transactions/logic/TEAL_opcodes_v9.md +++ b/data/transactions/logic/TEAL_opcodes_v9.md @@ -1485,7 +1485,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with - Bytecode: 0xb9 - Stack: ..., A: boxName, B: uint64 → ..., bool -- create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 +- create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1 - Availability: v8 - Mode: Application diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 19c75d201a..4fdcaae615 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -430,7 +430,13 @@ pushbytess "1" "2" "1" const v8Nonsense = v7Nonsense + switchNonsense + frameNonsense + matchNonsense + boxNonsense const v9Nonsense = v8Nonsense -const v10Nonsense = v9Nonsense + pairingNonsense + +const spliceNonsence = ` + box_splice + box_resize +` + +const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" @@ -447,7 +453,10 @@ const matchCompiled = "83030102018e02fff500008203013101320131" const v8Compiled = v7Compiled + switchCompiled + frameCompiled + matchCompiled + boxCompiled const v9Compiled = v8Compiled -const v10Compiled = v9Compiled + pairingCompiled + +const spliceCompiled = "d2d3" + +const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled var nonsense = map[uint64]string{ 1: v1Nonsense, @@ -527,7 +536,7 @@ func TestAssemble(t *testing.T) { } } -var experiments = []uint64{pairingVersion} +var experiments = []uint64{pairingVersion, spliceVersion} // TestExperimental forces a conscious choice to promote "experimental" opcode // groups. This will fail when we increment vFuture's LogicSigVersion. If we had diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index ad371c6a1f..4938f95aa9 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -35,6 +35,8 @@ const ( BoxWriteOperation // BoxDeleteOperation deletes a box BoxDeleteOperation + // BoxResizeOperation resizes a box + BoxResizeOperation ) func (cx *EvalContext) availableBox(name string, operation BoxOperation, createSize uint64) ([]byte, bool, error) { @@ -81,6 +83,13 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS cx.available.dirtyBytes += writeSize } dirty = true + case BoxResizeOperation: + newSize := createSize + if dirty { + cx.available.dirtyBytes -= uint64(len(content)) + } + cx.available.dirtyBytes += newSize + dirty = true case BoxDeleteOperation: if dirty { cx.available.dirtyBytes -= uint64(len(content)) @@ -199,6 +208,34 @@ func opBoxReplace(cx *EvalContext) error { return cx.Ledger.SetBox(cx.appID, name, bytes) } +func opBoxSplice(cx *EvalContext) error { + last := len(cx.Stack) - 1 // replacement + replacement := cx.Stack[last].Bytes + length := cx.Stack[last-1].Uint + start := cx.Stack[last-2].Uint + name := string(cx.Stack[last-3].Bytes) + + err := argCheck(cx, name, 0) + if err != nil { + return err + } + + contents, exists, err := cx.availableBox(name, BoxWriteOperation, 0 /* size is already known */) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("no such box %#x", name) + } + + bytes, err := spliceCarefully(contents, replacement, start, length) + if err != nil { + return err + } + cx.Stack = cx.Stack[:last-3] + return cx.Ledger.SetBox(cx.appID, name, bytes) +} + func opBoxDel(cx *EvalContext) error { last := len(cx.Stack) - 1 // name name := string(cx.Stack[last].Bytes) @@ -222,6 +259,48 @@ func opBoxDel(cx *EvalContext) error { return nil } +func opBoxResize(cx *EvalContext) error { + last := len(cx.Stack) - 1 // size + prev := last - 1 // name + + name := string(cx.Stack[prev].Bytes) + size := cx.Stack[last].Uint + + err := argCheck(cx, name, size) + if err != nil { + return err + } + + contents, exists, err := cx.availableBox(name, BoxResizeOperation, size) + if err != nil { + return err + } + + if !exists { + return fmt.Errorf("no such box %#x", name) + } + appAddr := cx.GetApplicationAddress(cx.appID) + _, err = cx.Ledger.DelBox(cx.appID, name, appAddr) + if err != nil { + return err + } + var resized []byte + if size > uint64(len(contents)) { + resized = make([]byte, size) + copy(resized, contents) + } else { + resized = contents[:size] + } + err = cx.Ledger.NewBox(cx.appID, name, resized, appAddr) + if err != nil { + return err + } + + cx.Stack = cx.Stack[:prev] + return err + +} + func opBoxLen(cx *EvalContext) error { last := len(cx.Stack) - 1 // name name := string(cx.Stack[last].Bytes) @@ -292,3 +371,34 @@ func opBoxPut(cx *EvalContext) error { appAddr := cx.GetApplicationAddress(cx.appID) return cx.Ledger.NewBox(cx.appID, name, value, appAddr) } + +// spliceCarefully is used to make a NEW byteslice copy of original, with +// replacement written over the bytes from start to start+length. Returned slice +// is always the same size as original. Zero bytes are "shifted in" or high +// bytes are "shifted out" as needed. +func spliceCarefully(original []byte, replacement []byte, start uint64, olen uint64) ([]byte, error) { + if start > uint64(len(original)) { + return nil, fmt.Errorf("replacement start %d beyond length: %d", start, len(original)) + } + oend := start + olen + if oend < start { + return nil, fmt.Errorf("splice end exceeds uint64") + } + + if oend > uint64(len(original)) { + return nil, fmt.Errorf("splice end %d beyond original length: %d", oend, len(original)) + } + + // Do NOT use the append trick to make a copy here. + // append(nil, []byte{}...) would return a nil, which means "not a bytearray" to AVM. + clone := make([]byte, len(original)) + copy(clone[:start], original) + copied := copy(clone[start:], replacement) + if copied != len(replacement) { + return nil, fmt.Errorf("splice inserted bytes too long") + } + // If original is "too short" we get zeros at the end. If original is "too + // long" we lose some bytes. Fortunately, that's what we want. + copy(clone[int(start)+copied:], original[oend:]) + return clone, nil +} diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 5f08878a93..6649627440 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -41,11 +41,15 @@ func TestBoxNewDel(t *testing.T) { ep, txn, ledger := MakeSampleEnv() createSelf := fmt.Sprintf(`byte "self"; int %d; box_create;`, size) + growSelf := fmt.Sprintf(`byte "self"; int %d; box_resize; int 1`, size+5) createOther := fmt.Sprintf(`byte "other"; int %d; box_create;`, size) ledger.NewApp(txn.Sender, 888, basics.AppParams{}) + TestApp(t, growSelf, ep, "no such box") + TestApp(t, createSelf, ep) + TestApp(t, growSelf, ep) ledger.DelBoxes(888, "self") TestApp(t, createSelf+`assert;`+createSelf+`!`, ep) @@ -77,10 +81,13 @@ func TestBoxNewBad(t *testing.T) { ledger.NewApp(txn.Sender, 888, basics.AppParams{}) TestApp(t, `byte "self"; int 999; box_create`, ep, "write budget") - // In test proto, you get 100 I/O budget per boxref + // In test proto, you get 100 I/O budget per boxref, and 1000 is the + // absolute biggest box. ten := [10]transactions.BoxRef{} txn.Boxes = append(txn.Boxes, ten[:]...) // write budget is now 11*100 = 1100 TestApp(t, `byte "self"; int 999; box_create`, ep) + TestApp(t, `byte "self"; int 1000; box_resize; int 1`, ep) + TestApp(t, `byte "self"; int 1001; box_resize; int 1`, ep, "box size too large") ledger.DelBoxes(888, "self") TestApp(t, `byte "self"; int 1000; box_create`, ep) ledger.DelBoxes(888, "self") @@ -139,6 +146,73 @@ func TestBoxReadWrite(t *testing.T) { "no such box") TestApp(t, `byte "junk"; int 1; byte 0x3031; box_replace`, ep, "invalid Box reference") + + TestApp(t, `byte "self"; int 1; int 2; byte 0x3031; box_splice`, ep, + "no such box") + TestApp(t, `byte "junk"; int 1; int 2; byte 0x3031; box_splice`, ep, + "invalid Box reference") +} + +func TestBoxSplice(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ep, txn, ledger := MakeSampleEnv() + + ledger.NewApp(txn.Sender, 888, basics.AppParams{}) + // extract some bytes until past the end, confirm the begin as zeros, and + // when it fails. + TestApp(t, `byte "self"; int 4; box_create;`, ep) + + // replace two bytes with two bytes. would usually use box_replace + TestApp(t, `byte "self"; int 1; int 2; byte 0x5555; box_splice; + byte "self"; box_get; assert; byte 0x00555500; ==`, ep) + + // replace first 55 with two 44s. + TestApp(t, `byte "self"; int 1; int 1; byte 0x4444; box_splice; + byte "self"; box_get; assert; byte 0x00444455; ==`, ep) + + // replace second 44 with two 33s. (loses the 55) + TestApp(t, `byte "self"; int 2; int 1; byte 0x3333; box_splice; + byte "self"; box_get; assert; byte 0x00443333; ==`, ep) + + // replace 0044 with 22. (shifts in a 0x00) + TestApp(t, `byte "self"; int 0; int 2; byte 0x22; box_splice; + byte "self"; box_get; assert; byte 0x22333300; ==`, ep) + + // dumb: try to replace 00 with 1111, but growing is illegal + TestApp(t, `byte "self"; int 3; int 1; byte 0x1111; box_splice; + byte "self"; box_get; assert; byte 0x2233331111; ==`, ep, + "inserted bytes too long") + + // dumber: try to replace 00__ with 1111, but placing outside bounds is illegal + TestApp(t, `byte "self"; int 3; int 2; byte 0x1111; box_splice; + byte "self"; box_get; assert; byte 0x2233331111; ==`, ep, + "splice end 5 beyond original length") + + // try to replace AT end (fails because it would extend) + TestApp(t, `byte "self"; int 4; int 0; byte 0x1111; box_splice; + byte "self"; box_get; assert; byte 0x223333001111; ==`, ep, + "splice inserted bytes too long") + + // so it's ok if you splice in nothing + TestApp(t, `byte "self"; int 4; int 0; byte 0x; box_splice; + byte "self"; box_get; assert; byte 0x22333300; ==`, ep) + + // try to replace BEYOND end (fails no matter what) + TestApp(t, `byte "self"; int 5; int 0; byte 0x1111; box_splice; + byte "self"; box_get; assert; byte 0x22333300001111; ==`, ep, + "replacement start 5 beyond length") + + // even doing nothing is illegal beyond the end + TestApp(t, `byte "self"; int 5; int 0; byte 0x; box_splice; + byte "self"; box_get; assert; byte 0x22333300; ==`, ep, + "replacement start 5 beyond length") + + // overflow doesn't work + TestApp(t, `byte "self"; int 2; int 18446744073709551615; byte 0x; box_splice; + byte "self"; box_get; assert; byte 0x22333300; ==`, ep, + "splice end exceeds uint64") } func TestBoxAcrossTxns(t *testing.T) { @@ -167,22 +241,37 @@ func TestDirtyTracking(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - ep, txn, ledger := MakeSampleEnv() + ep, txn, ledger := MakeSampleEnv() // has two box refs, "self", "other" = 200 budget ledger.NewApp(txn.Sender, 888, basics.AppParams{}) TestApp(t, `byte "self"; int 200; box_create`, ep) + TestApp(t, `byte "self"; int 201; box_resize; int 1`, ep, "write budget") TestApp(t, `byte "other"; int 201; box_create`, ep, "write budget") // deleting "self" doesn't give extra write budget to create big "other" - TestApp(t, `byte "self"; box_del; !; byte "other"; int 201; box_create`, ep, + TestApp(t, `byte "self"; box_del; assert; byte "other"; int 201; box_create`, ep, "write budget") // though it cancels out a creation that happened here TestApp(t, `byte "self"; int 200; box_create; assert byte "self"; box_del; assert - byte "self"; int 200; box_create; + byte "other"; int 200; box_create; + `, ep) + ledger.DelBoxes(888, "self", "other") + + // create 200, but shrink it, then the write budget frees up + TestApp(t, `byte "self"; int 200; box_create; assert + byte "self"; int 150; box_resize; + byte "other"; int 50; box_create; `, ep) + ledger.DelBoxes(888, "self", "other") + // confirm that the exactly right amount freed up + TestApp(t, `byte "self"; int 200; box_create; assert + byte "self"; int 150; box_resize; + byte "other"; int 51; box_create; + `, ep, "write budget") ledger.DelBoxes(888, "self", "other") + // same, but create a different box than deleted TestApp(t, `byte "self"; int 200; box_create; assert byte "self"; box_del; assert @@ -217,6 +306,7 @@ func TestBoxUnavailableWithClearState(t *testing.T) { "box_len": `byte "self"; box_len`, "box_put": `byte "put"; byte "self"; box_put`, "box_replace": `byte "self"; int 0; byte "new"; box_replace`, + "box_resize": `byte "self"; int 10; box_resize`, } for name, program := range tests { @@ -523,6 +613,8 @@ func TestEarlyPanics(t *testing.T) { "box_len": `byte "%s"; box_len`, "box_put": `byte "%s"; byte "hello"; box_put`, "box_replace": `byte "%s"; int 0; byte "new"; box_replace`, + "box_splice": `byte "%s"; int 0; int 2; byte "new"; box_splice`, + "box_resize": `byte "%s"; int 2; box_resize`, } for name, program := range tests { diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index c060d82623..bfecb01a67 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -286,13 +286,15 @@ var opDescByName = map[string]OpDesc{ "frame_bury": {"replace the Nth (signed) value from the frame pointer in the stack with A", "", []string{"frame slot"}}, "popn": {"remove N values from the top of the stack", "", []string{"stack depth"}}, - "box_create": {"create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", nil}, + "box_create": {"create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", nil}, "box_extract": {"read C bytes from box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "", nil}, "box_replace": {"write byte-array C into box A, starting at offset B. Fail if A does not exist, or the byte range is outside A's size.", "", nil}, + "box_splice": {"set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C.", "Boxes are of constant length. If C < len(D), then len(D)-C bytes will be removed from the end. If C > len(D), zero bytes will be appended to the end to reach the box length.", nil}, "box_del": {"delete box named A if it exists. Return 1 if A existed, 0 otherwise", "", nil}, "box_len": {"X is the length of box A if A exists, else 0. Y is 1 if A exists, else 0.", "", nil}, "box_get": {"X is the contents of box A if A exists, else ''. Y is 1 if A exists, else 0.", "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", nil}, "box_put": {"replaces the contents of box A with byte-array B. Fails if A exists and len(B) != len(box A). Creates A if it does not exist", "For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `box_replace`", nil}, + "box_resize": {"change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768.", "", nil}, } // OpDoc returns a description of the op @@ -351,7 +353,7 @@ var OpGroups = map[string][]string{ "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "pushints", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "pushbytess", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "popn", "dup", "dup2", "dupn", "dig", "bury", "cover", "uncover", "frame_dig", "frame_bury", "swap", "select", "assert", "callsub", "proto", "retsub", "switch", "match"}, "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log", "block"}, - "Box Access": {"box_create", "box_extract", "box_replace", "box_del", "box_len", "box_get", "box_put"}, + "Box Access": {"box_create", "box_extract", "box_replace", "box_splice", "box_del", "box_len", "box_get", "box_put", "box_resize"}, "Inner Transactions": {"itxn_begin", "itxn_next", "itxn_field", "itxn_submit", "itxn", "itxna", "itxnas", "gitxn", "gitxna", "gitxnas"}, } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 2db51b24a7..0e1faec0de 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -3962,7 +3962,7 @@ func replaceCarefully(original []byte, replacement []byte, start uint64) ([]byte return nil, fmt.Errorf("replacement start %d beyond length: %d", start, len(original)) } end := start + uint64(len(replacement)) - if end < start { // impossible because it is sum of two avm value lengths + if end < start { // impossible because it is sum of two avm value (or box) lengths return nil, fmt.Errorf("replacement end exceeds uint64") } diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json index 7ee54fb98d..affe46601e 100644 --- a/data/transactions/logic/langspec_v10.json +++ b/data/transactions/logic/langspec_v10.json @@ -4242,7 +4242,7 @@ ], "Size": 1, "DocCost": "1", - "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", + "Doc": "create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", "IntroducedVersion": 8, "Groups": [ @@ -4644,6 +4644,39 @@ "State Access" ] }, + { + "Opcode": 210, + "Name": "box_splice", + "Args": [ + "boxName", + "uint64", + "uint64", + "[]byte" + ], + "Size": 1, + "DocCost": "1", + "Doc": "set box A to contain its previous bytes up to index B, followed by D, followed by the original bytes of A that began at index B+C.", + "DocExtra": "Boxes are of constant length. If C \u003c len(D), then len(D)-C bytes will be removed from the end. If C \u003e len(D), zero bytes will be appended to the end to reach the box length.", + "IntroducedVersion": 10, + "Groups": [ + "Box Access" + ] + }, + { + "Opcode": 211, + "Name": "box_resize", + "Args": [ + "boxName", + "uint64" + ], + "Size": 1, + "DocCost": "1", + "Doc": "change the size of box named A to be of length B, adding zero bytes to end or removing bytes from the end, as needed. Fail if the name A is empty, A is not an existing box, or B exceeds 32,768.", + "IntroducedVersion": 10, + "Groups": [ + "Box Access" + ] + }, { "Opcode": 224, "Name": "ec_add", diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json index 3b496ddcb8..4963f4c85a 100644 --- a/data/transactions/logic/langspec_v8.json +++ b/data/transactions/logic/langspec_v8.json @@ -4238,7 +4238,7 @@ ], "Size": 1, "DocCost": "1", - "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", + "Doc": "create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", "IntroducedVersion": 8, "Groups": [ diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json index c52d36862d..50418be824 100644 --- a/data/transactions/logic/langspec_v9.json +++ b/data/transactions/logic/langspec_v9.json @@ -4238,7 +4238,7 @@ ], "Size": 1, "DocCost": "1", - "Doc": "create a box named A, of length B. Fail if A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", + "Doc": "create a box named A, of length B. Fail if the name A is empty or B exceeds 32,768. Returns 0 if A already existed, else 1", "DocExtra": "Newly created boxes are filled with 0 bytes. `box_create` will fail if the referenced box already exists with a different size. Otherwise, existing boxes are unchanged by `box_create`.", "IntroducedVersion": 8, "Groups": [ diff --git a/data/transactions/logic/opcodeExplain.go b/data/transactions/logic/opcodeExplain.go index 4b4f965a65..3643ade21f 100644 --- a/data/transactions/logic/opcodeExplain.go +++ b/data/transactions/logic/opcodeExplain.go @@ -197,6 +197,12 @@ func opBoxReplaceStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, bas return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[pprev].Bytes) } +func opBoxSpliceStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) { + name := len(cx.Stack) - 4 // name, start, length, replacement + + return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[name].Bytes) +} + func opBoxDelStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) { last := len(cx.Stack) - 1 // name @@ -210,6 +216,12 @@ func opBoxPutStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics. return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[prev].Bytes) } +func opBoxResizeStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) { + name := len(cx.Stack) - 2 // name, size + + return BoxState, AppStateWrite, cx.appID, basics.Address{}, string(cx.Stack[name].Bytes) +} + func opAppLocalGetStateChange(cx *EvalContext) (AppStateEnum, AppStateOpEnum, basics.AppIndex, basics.Address, string) { last := len(cx.Stack) - 1 // state key prev := last - 1 // account diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index dc2d44bc09..fa3fd22625 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -75,6 +75,7 @@ const sharedResourcesVersion = 9 // apps can access resources from other transac // moved from vFuture to a new consensus version. If they remain unready, bump // their version, and fixup TestAssemble() in assembler_test.go. const pairingVersion = 10 // bn256 opcodes. will add bls12-381, and unify the available opcodes. +const spliceVersion = 10 // box splicing/resizing // Unlimited Global Storage opcodes const boxVersion = 8 // box_* @@ -721,6 +722,8 @@ var OpSpecs = []OpSpec{ // randomness support {0xd0, "vrf_verify", opVrfVerify, proto("b83:bT"), randomnessVersion, field("s", &VrfStandards).costs(5700)}, {0xd1, "block", opBlock, proto("i:a"), randomnessVersion, field("f", &BlockFields)}, + {0xd2, "box_splice", opBoxSplice, proto("Niib:").appStateExplain(opBoxSpliceStateChange), spliceVersion, only(ModeApp)}, + {0xd3, "box_resize", opBoxResize, proto("Ni:").appStateExplain(opBoxResizeStateChange), spliceVersion, only(ModeApp)}, {0xe0, "ec_add", opEcAdd, proto("bb:b"), pairingVersion, costByField("g", &EcGroups, []int{ diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 56cf7b5dc8..ef80fd048a 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -72,7 +72,7 @@ }, { "name": "keyword.other.unit.teal", - "match": "^(box_create|box_del|box_extract|box_get|box_len|box_put|box_replace|acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|block|log|min_balance)\\b" + "match": "^(box_create|box_del|box_extract|box_get|box_len|box_put|box_replace|box_resize|box_splice|acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|block|log|min_balance)\\b" }, { "name": "keyword.operator.teal", From 65923004094044e053f8ae25097737c7c2ae1f18 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 6 Dec 2023 12:39:14 -0500 Subject: [PATCH 029/218] network: Use peer address after proxy fix for app rate limiter if available (#5848) --- config/localTemplate.go | 6 + network/requestTracker.go | 31 +++- network/requestTracker_test.go | 101 +++++++++++ network/websocketProxy_test.go | 322 +++++++++++++++++++++++++++++++++ network/wsPeer.go | 11 +- network/wsPeer_test.go | 6 + 6 files changed, 470 insertions(+), 7 deletions(-) create mode 100644 network/websocketProxy_test.go diff --git a/config/localTemplate.go b/config/localTemplate.go index 61c2381fa0..25c6edab05 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -311,6 +311,12 @@ type Local struct { // determining the source of a connection. If used, it should be set to the string "X-Forwarded-For", unless the // proxy vendor provides another header field. In the case of CloudFlare proxy, the "CF-Connecting-IP" header // field can be used. + // This setting does not support multiple X-Forwarded-For HTTP headers or multiple values in in the header and always uses the last value + // from the last X-Forwarded-For HTTP header that corresponds to a single reverse proxy (even if it received the request from another reverse proxy or adversary node). + // + // WARNING: By enabling this option, you are trusting peers to provide accurate forwarding addresses. + // Bad actors can easily spoof these headers to circumvent this node's rate and connection limiting + // logic. Do not enable this if your node is publicly reachable or used by untrusted parties. UseXForwardedForAddressField string `version[0]:""` // ForceRelayMessages indicates whether the network library should relay messages even in the case that no NetAddress was specified. diff --git a/network/requestTracker.go b/network/requestTracker.go index 025d75a5a6..6445b93497 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -20,7 +20,10 @@ import ( "fmt" "net" "net/http" + "net/textproto" "sort" + "strings" + "sync/atomic" "time" "github.com/algorand/go-deadlock" @@ -222,7 +225,7 @@ type RequestTracker struct { log logging.Logger config config.Local // once we detect that we have a misconfigured UseForwardedForAddress, we set this and write an warning message. - misconfiguredUseForwardedForAddress bool + misconfiguredUseForwardedForAddress atomic.Bool listener net.Listener // this is the downsteam listener @@ -518,13 +521,29 @@ func (rt *RequestTracker) getForwardedConnectionAddress(header http.Header) (ip if rt.config.UseXForwardedForAddressField == "" { return } - forwardedForString := header.Get(rt.config.UseXForwardedForAddressField) + var forwardedForString string + // if we're using the standard X-Forwarded-For header(s), we need to parse it. + // as UseXForwardedForAddressField defines, use the last value from the last X-Forwarded-For header's list of values. + if textproto.CanonicalMIMEHeaderKey(rt.config.UseXForwardedForAddressField) == "X-Forwarded-For" { + forwardedForStrings := header.Values(rt.config.UseXForwardedForAddressField) + if len(forwardedForStrings) != 0 { + forwardedForString = forwardedForStrings[len(forwardedForStrings)-1] + ips := strings.Split(forwardedForString, ",") + if len(ips) != 0 { + forwardedForString = strings.TrimSpace(ips[len(ips)-1]) + } else { + // looks like not possble case now but it's better to handle + rt.log.Warnf("header X-Forwarded-For has an invalid value: '%s'", forwardedForString) + forwardedForString = "" + } + } + } else { + forwardedForString = header.Get(rt.config.UseXForwardedForAddressField) + } + if forwardedForString == "" { - rt.httpConnectionsMu.Lock() - defer rt.httpConnectionsMu.Unlock() - if !rt.misconfiguredUseForwardedForAddress { + if rt.misconfiguredUseForwardedForAddress.CompareAndSwap(false, true) { rt.log.Warnf("UseForwardedForAddressField is configured as '%s', but no value was retrieved from header", rt.config.UseXForwardedForAddressField) - rt.misconfiguredUseForwardedForAddress = true } return } diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index 8998b41f77..1941069147 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -17,7 +17,9 @@ package network import ( + "bytes" "math/rand" + "net/http" "testing" "time" @@ -172,6 +174,7 @@ func TestRateLimiting(t *testing.T) { func TestIsLocalHost(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() require.True(t, isLocalhost("localhost")) require.True(t, isLocalhost("127.0.0.1")) @@ -183,3 +186,101 @@ func TestIsLocalHost(t *testing.T) { require.False(t, isLocalhost("0.0.0.0")) require.False(t, isLocalhost("127.0.0.0")) } + +func TestGetForwardedConnectionAddress(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var bufNewLogger bytes.Buffer + log := logging.NewLogger() + log.SetOutput(&bufNewLogger) + + rt := RequestTracker{log: log} + header := http.Header{} + + ip := rt.getForwardedConnectionAddress(header) + require.Nil(t, ip) + msgs := bufNewLogger.String() + require.Empty(t, msgs) + + rt.config.UseXForwardedForAddressField = "X-Custom-Addr" + ip = rt.getForwardedConnectionAddress(header) + require.Nil(t, ip) + msgs = bufNewLogger.String() + require.NotEmpty(t, msgs) + require.Contains(t, msgs, "UseForwardedForAddressField is configured as 'X-Custom-Addr'") + + // try again and ensure the message is not logged second time. + bufNewLogger.Reset() + ip = rt.getForwardedConnectionAddress(header) + require.Nil(t, ip) + msgs = bufNewLogger.String() + require.Empty(t, msgs) + + // check a custom address can be parsed successfully. + header.Set("X-Custom-Addr", "123.123.123.123") + ip = rt.getForwardedConnectionAddress(header) + require.NotNil(t, ip) + require.Equal(t, "123.123.123.123", ip.String()) + msgs = bufNewLogger.String() + require.Empty(t, msgs) + + // check a custom address in a form of a list can not be parsed, + // this is the original behavior since the Release. + header.Set("X-Custom-Addr", "123.123.123.123, 234.234.234.234") + ip = rt.getForwardedConnectionAddress(header) + require.Nil(t, ip) + msgs = bufNewLogger.String() + require.NotEmpty(t, msgs) + require.Contains(t, msgs, "unable to parse origin address") + + // "X-Forwarded-For + bufNewLogger.Reset() + rt.misconfiguredUseForwardedForAddress.Store(false) + rt.config.UseXForwardedForAddressField = "X-Forwarded-For" + header = http.Header{} + + // check "X-Forwarded-For" empty value. + ip = rt.getForwardedConnectionAddress(header) + require.Nil(t, ip) + msgs = bufNewLogger.String() + require.NotEmpty(t, msgs) + require.Contains(t, msgs, "UseForwardedForAddressField is configured as 'X-Forwarded-For'") + bufNewLogger.Reset() + + // check "X-Forwarded-For" single value. + header.Set("X-Forwarded-For", "123.123.123.123") + ip = rt.getForwardedConnectionAddress(header) + require.NotNil(t, ip) + require.Equal(t, "123.123.123.123", ip.String()) + msgs = bufNewLogger.String() + require.Empty(t, msgs) + + // check "X-Forwarded-For" list values - the last one is used, + // this is a new behavior. + bufNewLogger.Reset() + rt.config.UseXForwardedForAddressField = "X-Forwarded-For" + header.Set("X-Forwarded-For", "123.123.123.123, 234.234.234.234") + ip = rt.getForwardedConnectionAddress(header) + require.NotNil(t, ip) + require.Equal(t, "234.234.234.234", ip.String()) + msgs = bufNewLogger.String() + require.Empty(t, msgs) + + // check multile X-Forwarded-For headers - the last one should be used + header.Set("X-Forwarded-For", "127.0.0.1") + header.Add("X-Forwarded-For", "234.234.234.234") + ip = rt.getForwardedConnectionAddress(header) + require.NotNil(t, ip) + require.Equal(t, "234.234.234.234", ip.String()) + msgs = bufNewLogger.String() + require.Empty(t, msgs) + + header.Set("X-Forwarded-For", "127.0.0.1") + header.Add("X-Forwarded-For", "123.123.123.123, 234.234.234.234") + ip = rt.getForwardedConnectionAddress(header) + require.NotNil(t, ip) + require.Equal(t, "234.234.234.234", ip.String()) + msgs = bufNewLogger.String() + require.Empty(t, msgs) +} diff --git a/network/websocketProxy_test.go b/network/websocketProxy_test.go new file mode 100644 index 0000000000..e0888c5449 --- /dev/null +++ b/network/websocketProxy_test.go @@ -0,0 +1,322 @@ +// 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 . + +// This is a simple reverse proxy for websocket connections. It is used to to test +// ws network behavior when UseXForwardedForAddressField is enabled. +// Not suitable for production use. +package network + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "testing" + "time" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/websocket" + "github.com/stretchr/testify/require" +) + +var testProxyUpgrader = websocket.Upgrader{ + ReadBufferSize: 4096, + WriteBufferSize: 4096, + EnableCompression: false, +} + +var testProxyDialer = net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, +} + +var testProxyWebsocketDialer = websocket.Dialer{ + HandshakeTimeout: 45 * time.Second, + EnableCompression: false, + NetDialContext: testProxyDialer.DialContext, + NetDial: testProxyDialer.Dial, + MaxHeaderSize: wsMaxHeaderBytes, +} + +type websocketProxy struct { + upstream string + overrideXForwardedFor string +} + +// ServeHTTP implements http.Handler +func (w *websocketProxy) ServeHTTP(response http.ResponseWriter, request *http.Request) { + // copy all but upgrade headers otherwise Dial complains about duplicate headers + headers := http.Header{} + for k, v := range request.Header { + // filter out upgrade headers since Upgrader will add them + if k == "Sec-Websocket-Key" || k == "Sec-Websocket-Version" || k == "Connection" || k == "Upgrade" { + continue + } + headers[k] = v + } + + // set X-Forwarded-For + url, err := ParseHostOrURL(request.RemoteAddr) + if err != nil { + http.Error(response, err.Error(), http.StatusInternalServerError) + return + } + if w.overrideXForwardedFor != "" { + headers.Set("X-Forwarded-For", w.overrideXForwardedFor) + } else { + headers.Set("X-Forwarded-For", url.Hostname()) + } + + upURL := *request.URL + upURL.Host = w.upstream + upURL.Scheme = "ws" + + // dial upstream + upstreamConn, upResp, err := testProxyWebsocketDialer.Dial(upURL.String(), headers) + if err != nil { + msg := fmt.Sprintf("websocketProxy: error dialing upstream %s: %s", upURL.String(), err.Error()) + if upResp != nil { + msg = fmt.Sprintf("%s: %v", msg, *upResp) + } + http.Error(response, msg, http.StatusInternalServerError) + return + } + defer upstreamConn.Close() + + // upgeade the client + remoteConn, err := testProxyUpgrader.Upgrade(response, request, upResp.Header) + if err != nil { + http.Error(response, "websocketProxy: error upgrading connection: "+err.Error(), http.StatusInternalServerError) + return + } + + defer remoteConn.Close() + + remoteConn.SetReadLimit(MaxMessageLength) + upstreamConn.SetReadLimit(MaxMessageLength) + + errCh := make(chan error, 1) + go w.forward(remoteConn, upstreamConn, errCh) + go w.forward(upstreamConn, remoteConn, errCh) + + err = <-errCh + if e, ok := err.(*websocket.CloseError); !ok { + // calling http.Error causes "response.WriteHeader on hijacked connection" error + fmt.Printf("websocketProxy: closing error forwarding connection: %s\n", err.Error()) + } else if e.Code != websocket.CloseNormalClosure { + fmt.Printf("websocketProxy: closing error forwarding connection: %s\n", err.Error()) + } +} + +func (w *websocketProxy) forward(dst, src *websocket.Conn, errCh chan error) { + for { + msgType, msg, err := src.ReadMessage() + if err != nil { + errCh <- err + return + } + err = dst.WriteMessage(msgType, msg) + if err != nil { + errCh <- err + return + } + } +} + +// TestWebsocketProxy checks the websocket proxy implementation: +// it forwards messages ands adds X-Forwarded-For header +func TestWebsocketProxy(t *testing.T) { + partitiontest.PartitionTest(t) + + var headerChecker func(headers http.Header) // define below when all addresses are known + + // setup the upstream server + upstreamAddr := "127.0.0.1:" + upstreamMux := http.NewServeMux() + upstreamMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {}) + upstreamMux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + // handler returns the same message it receives with a prefix 'pong:' + t.Logf("upsream received connection from %s\n", r.RemoteAddr) + headerChecker(r.Header) + + conn, err := testProxyUpgrader.Upgrade(w, r, nil) + require.NoError(t, err) + conn.SetReadLimit(2 * 1024) + messageType, p, err := conn.ReadMessage() + require.NoError(t, err) + msg := append([]byte("pong:"), p...) + conn.WriteMessage(messageType, msg) + require.NoError(t, err) + }) + upstreamListener, err := net.Listen("tcp", upstreamAddr) + require.NoError(t, err) + upstreamAddr = upstreamListener.Addr().String() + upstreamSrv := &http.Server{Addr: upstreamAddr, Handler: upstreamMux} + go upstreamSrv.Serve(upstreamListener) + + // wait upstream to be ready + require.Eventually(t, func() bool { + resp, err := http.Get("http://" + upstreamAddr + "/status") + if err != nil { + return false + } + return resp.StatusCode == http.StatusOK + }, 5*time.Second, 100*time.Millisecond) + + // setup the proxy + wsProxy := &websocketProxy{upstreamAddr, ""} + wsProxyMux := http.NewServeMux() + wsProxyMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {}) + wsProxyMux.Handle("/ws", wsProxy) + wsProxyListener, err := net.Listen("tcp", "[::1]:") + require.NoError(t, err) + + wsProxyAddr := wsProxyListener.Addr().String() + wsProxySrv := &http.Server{Addr: wsProxyAddr, Handler: wsProxyMux} + go wsProxySrv.Serve(wsProxyListener) + + checked := false + headerChecker = func(headers http.Header) { + hostname, _, err := net.SplitHostPort(wsProxyAddr) + require.NoError(t, err) + require.Contains(t, headers, ("X-Forwarded-For")) + require.Equal(t, hostname, headers.Get("X-Forwarded-For")) + checked = true + } + + // wait ws proxy to be ready + require.Eventually(t, func() bool { + resp, err := http.Get("http://" + wsProxyAddr + "/status") + if err != nil { + return false + } + return resp.StatusCode == http.StatusOK + }, 5*time.Second, 100*time.Millisecond) + + t.Logf("upstream addr: %s", upstreamAddr) + t.Logf("ws proxy addr: %s", wsProxyAddr) + + // now send data through the proxy + conn, resp, err := testProxyWebsocketDialer.Dial("ws://"+wsProxyAddr+"/ws", nil) + var errMsg string + if err != nil && resp != nil { + b, err0 := io.ReadAll(resp.Body) + require.NoError(t, err0) + errMsg = fmt.Sprintf("error dialing proxy: %v, body: %s", resp, b) + } + require.NoError(t, err, errMsg) + t.Logf("connected to %s", conn.RemoteAddr().String()) + + conn.SetReadLimit(2 * 1024) + msg := "ping" + conn.WriteMessage(websocket.TextMessage, []byte(msg)) + require.NoError(t, err) + messageType, p, err := conn.ReadMessage() + require.NoError(t, err) + require.Equal(t, websocket.TextMessage, messageType) + require.Equal(t, "pong:"+msg, string(p)) + + conn.Close() + err = upstreamSrv.Shutdown(context.Background()) + require.NoError(t, err) + err = wsProxySrv.Shutdown(context.Background()) + require.NoError(t, err) + + // ensure the header was checked + require.True(t, checked) +} + +func TestWebsocketProxyWsNet(t *testing.T) { + partitiontest.PartitionTest(t) + + // upstream node + netA := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netA"}) + netA.requestsTracker.config.UseXForwardedForAddressField = "X-Forwarded-For" + netA.Start() + defer netA.Stop() + addrA, ok := netA.Address() + require.True(t, ok) + gossipA, err := netA.addrToGossipAddr(addrA) + require.NoError(t, err) + + parsedA, err := ParseHostOrURL(gossipA) + require.NoError(t, err) + + // setup the proxy + // use a fake address since all nodes are on the same machine/localhost + fakeXForwardedFor := "169.254.1.1" + wsProxy := &websocketProxy{parsedA.Host, fakeXForwardedFor} + wsProxyMux := http.NewServeMux() + wsProxyMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {}) + wsProxyMux.Handle(parsedA.Path, wsProxy) + wsProxyListener, err := net.Listen("tcp", "[::1]:") + require.NoError(t, err) + + wsProxyAddr := wsProxyListener.Addr().String() + wsProxySrv := &http.Server{Addr: wsProxyAddr, Handler: wsProxyMux} + go wsProxySrv.Serve(wsProxyListener) + defer wsProxySrv.Shutdown(context.Background()) + + // wait ws proxy to be ready + require.Eventually(t, func() bool { + resp, err := http.Get("http://" + wsProxyAddr + "/status") + if err != nil { + return false + } + return resp.StatusCode == http.StatusOK + }, 5*time.Second, 100*time.Millisecond) + + netB := makeTestWebsocketNode(t, testWebsocketLogNameOption{"netB"}) + netB.Start() + defer netB.Stop() + addrB, ok := netB.Address() + require.True(t, ok) + + t.Logf("upstream addr: %s", addrA) + t.Logf("ws proxy addr: %s", wsProxyAddr) + t.Logf("client netB addr: %s", addrB) + + require.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) + require.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut))) + require.Equal(t, 0, len(netB.GetPeers(PeersConnectedIn))) + require.Equal(t, 0, len(netB.GetPeers(PeersConnectedOut))) + + wsProxyGossip, ok := netB.tryConnectReserveAddr(wsProxyAddr) + require.True(t, ok) + + netB.wg.Add(1) + netB.tryConnect(wsProxyAddr, wsProxyGossip) + + require.Eventually(t, func() bool { + return len(netB.GetPeers(PeersConnectedOut)) == 1 + }, 5*time.Second, 10*time.Millisecond) + + require.Equal(t, 1, len(netA.GetPeers(PeersConnectedIn))) + require.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut))) + require.Equal(t, 0, len(netB.GetPeers(PeersConnectedIn))) + require.Equal(t, 1, len(netB.GetPeers(PeersConnectedOut))) + + // get peerB from the upstream node (netA) + // and ensure it has the expected origin/routing address as set by the proxy + peerB := netA.peers[0] + require.NotEmpty(t, peerB.originAddress) + require.Equal(t, fakeXForwardedFor, peerB.originAddress) + require.NotEqual(t, peerB.RoutingAddr(), peerB.IPAddr()) + fakeXForwardedForParsed := net.ParseIP(fakeXForwardedFor) + require.NotEqual(t, fakeXForwardedForParsed, peerB.RoutingAddr()) +} diff --git a/network/wsPeer.go b/network/wsPeer.go index 55dba8e568..4ed32539a7 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -404,7 +404,16 @@ func (wp *wsPeer) RoutingAddr() []byte { return true } - ip := wp.IPAddr() + var ip []byte + // originAddress is set for incoming connections + // and optionally includes reverse proxy support. + // see RequestTracker.getForwardedConnectionAddress for details. + if wp.wsPeerCore.originAddress != "" { + ip = net.ParseIP(wp.wsPeerCore.originAddress) + } else { + ip = wp.IPAddr() + } + if len(ip) != net.IPv6len { return ip } diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go index 59217047ce..91cd3cb37d 100644 --- a/network/wsPeer_test.go +++ b/network/wsPeer_test.go @@ -310,4 +310,10 @@ func TestWsPeerIPAddr(t *testing.T) { 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()) + + // check incoming peer with originAddress set + conn.addr.IP = []byte{127, 0, 0, 1} + peer.wsPeerCore.originAddress = "127.0.0.2" + require.Equal(t, []byte{127, 0, 0, 1}, peer.IPAddr()) + require.Equal(t, []byte{127, 0, 0, 2}, peer.RoutingAddr()) } From f48be99b1fb9949d98e5477e10c3380eb6fdef0a Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:32:18 -0500 Subject: [PATCH 030/218] tests: dump logs if e2e test node exited with an error (#5856) --- test/framework/fixtures/libgoalFixture.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 1cc0b24fb8..3b54f9f7aa 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -119,6 +119,10 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e if f.t == nil { return } + + f.t.Logf("Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err) + f.dumpLogs(filepath.Join(nc.GetDataDir(), "node.log")) + exitError, ok := err.(*exec.ExitError) if !ok { require.NoError(f.t, err, "Node at %s has terminated with an error", nc.GetDataDir()) From 4291e536b0aa94d6452f0d16e93395187d328e1f Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:05:17 -0500 Subject: [PATCH 031/218] tests: wait longer in TestTotalWeightChanges for larger nightly test network (#5841) --- test/e2e-go/features/stateproofs/stateproofs_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index 4631ecd6e9..287553acf9 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -843,7 +843,11 @@ func TestTotalWeightChanges(t *testing.T) { richNode.goOffline(a, &fixture, rnd) } - a.NoError(fixture.WaitForRound(rnd, 30*time.Second)) + if testing.Short() { + a.NoError(fixture.WaitForRound(rnd, 30*time.Second)) + } else { + a.NoError(fixture.WaitForRound(rnd, 60*time.Second)) + } blk, err := libgoal.BookkeepingBlock(rnd) a.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) From e63b634ac43ddc2af9ed1faffbd6f761e713ed52 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:51:14 -0500 Subject: [PATCH 032/218] Round Times: Set minimum dynamic filter timeout to 2500ms. (#5853) --- agreement/dynamicFilterTimeoutParams.go | 2 +- agreement/router.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/agreement/dynamicFilterTimeoutParams.go b/agreement/dynamicFilterTimeoutParams.go index 36348615b5..8b02bf082d 100644 --- a/agreement/dynamicFilterTimeoutParams.go +++ b/agreement/dynamicFilterTimeoutParams.go @@ -31,7 +31,7 @@ const dynamicFilterCredentialArrivalHistory int = 40 // DynamicFilterTimeoutLowerBound specifies a minimal duration that the // filter timeout must meet. -const dynamicFilterTimeoutLowerBound time.Duration = 500 * time.Millisecond +const dynamicFilterTimeoutLowerBound time.Duration = 2500 * time.Millisecond // DynamicFilterTimeoutCredentialArrivalHistoryIdx specified which sample to use // out of a sorted DynamicFilterCredentialArrivalHistory-sized array of time diff --git a/agreement/router.go b/agreement/router.go index 6ab144470f..86b9a18774 100644 --- a/agreement/router.go +++ b/agreement/router.go @@ -59,7 +59,16 @@ var credentialRoundLag round func init() { // credential arrival time should be at most 2*config.Protocol.SmallLambda after it was sent + // Note that the credentialRoundLag is inversely proportional to the dynamicFilterTimeoutLowerBound + // in the default formula. Since we are adjusting this lower bound over time, + // for consistency in analytics we are setting the minimum to be 8 rounds + // (equivalent to a dynamicFilterTimeoutLowerBound of 500 ms). + minCredentialRoundLag := round(8) // round 2*2000ms / 500ms credentialRoundLag = round(2 * config.Protocol.SmallLambda / dynamicFilterTimeoutLowerBound) + + if credentialRoundLag < minCredentialRoundLag { + credentialRoundLag = minCredentialRoundLag + } if credentialRoundLag*round(dynamicFilterTimeoutLowerBound) < round(2*config.Protocol.SmallLambda) { credentialRoundLag++ } From 34d67999bbe9860bbc7435b9630a197acbeca51e Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Thu, 7 Dec 2023 18:47:44 +0000 Subject: [PATCH 033/218] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index d00491fd7e..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/config/version.go b/config/version.go index 17bd00a9b5..f7e656d40d 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 20 +const VersionMinor = 21 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From ebd35930d5865c6688e371149e10f3393c622948 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:11:49 -0500 Subject: [PATCH 034/218] Round Times: Period 0 deadline timeout (#5850) Co-authored-by: John Jannotti --- agreement/actions.go | 3 +- agreement/player.go | 23 +++++++++----- agreement/service_test.go | 36 +++++++++++----------- agreement/types.go | 22 ++++++++----- catchup/service.go | 16 +++++----- catchup/service_test.go | 20 ++++++------ config/consensus.go | 9 ++++-- daemon/algod/api/server/common/handlers.go | 4 +-- 8 files changed, 77 insertions(+), 56 deletions(-) diff --git a/agreement/actions.go b/agreement/actions.go index 0e0d5f19c4..ef2dd76c2b 100644 --- a/agreement/actions.go +++ b/agreement/actions.go @@ -232,8 +232,7 @@ type ensureAction struct { Payload proposal // the certificate proving commitment Certificate Certificate - - // The time that the winning proposal-vote was validated, relative to the beginning of the round + // The time that the winning proposal-vote was validated for round credentialRoundLag back from the current one voteValidatedAt time.Duration // The dynamic filter timeout calculated for this round, even if not enabled, for reporting to telemetry. dynamicFilterTimeout time.Duration diff --git a/agreement/player.go b/agreement/player.go index 21d65ecf88..4e7ec685e3 100644 --- a/agreement/player.go +++ b/agreement/player.go @@ -100,10 +100,19 @@ func (p *player) handle(r routerHandle, e event) []action { r.t.logTimeout(*p) } + var deadlineTimeout time.Duration + if e.Proto.Version == "" || e.Proto.Err != nil { + r.t.log.Errorf("failed to read valid protocol version for timeout event (proto %v): %v. "+ + "Falling Back to default deadline timeout.", e.Proto.Version, e.Proto.Err) + deadlineTimeout = DefaultDeadlineTimeout() + } else { + deadlineTimeout = DeadlineTimeout(p.Period, e.Proto.Version) + } + switch p.Step { case soft: // precondition: nap = false - actions = p.issueSoftVote(r) + actions = p.issueSoftVote(r, deadlineTimeout) p.Step = cert // update tracer state to match player r.t.setMetadata(tracerMetadata{p.Round, p.Period, p.Step}) @@ -113,16 +122,16 @@ func (p *player) handle(r routerHandle, e event) []action { p.Step = next // update tracer state to match player r.t.setMetadata(tracerMetadata{p.Round, p.Period, p.Step}) - return p.issueNextVote(r) + return p.issueNextVote(r, deadlineTimeout) default: if p.Napping { - return p.issueNextVote(r) // sets p.Napping to false + return p.issueNextVote(r, deadlineTimeout) // sets p.Napping to false } // not napping, so we should enter a new step p.Step++ // note: this must happen before next timeout setting. // TODO add unit test to ensure that deadlines increase monotonically here - lower, upper := p.Step.nextVoteRanges() + lower, upper := p.Step.nextVoteRanges(deadlineTimeout) delta := time.Duration(e.RandomEntropy % uint64(upper-lower)) p.Napping = true @@ -158,7 +167,7 @@ func (p *player) handleFastTimeout(r routerHandle, e timeoutEvent) []action { return p.issueFastVote(r) } -func (p *player) issueSoftVote(r routerHandle) (actions []action) { +func (p *player) issueSoftVote(r routerHandle, deadlineTimeout time.Duration) (actions []action) { defer func() { p.Deadline = Deadline{Duration: deadlineTimeout, Type: TimeoutDeadline} }() @@ -202,7 +211,7 @@ func (p *player) issueCertVote(r routerHandle, e committableEvent) action { return pseudonodeAction{T: attest, Round: p.Round, Period: p.Period, Step: cert, Proposal: e.Proposal} } -func (p *player) issueNextVote(r routerHandle) []action { +func (p *player) issueNextVote(r routerHandle, deadlineTimeout time.Duration) []action { actions := p.partitionPolicy(r) a := pseudonodeAction{T: attest, Round: p.Round, Period: p.Period, Step: p.Step, Proposal: bottom} @@ -226,7 +235,7 @@ func (p *player) issueNextVote(r routerHandle) []action { r.t.timeR().RecStep(p.Period, p.Step, a.Proposal) - _, upper := p.Step.nextVoteRanges() + _, upper := p.Step.nextVoteRanges(deadlineTimeout) p.Napping = false p.Deadline = Deadline{Duration: upper, Type: TimeoutDeadline} return actions diff --git a/agreement/service_test.go b/agreement/service_test.go index f6ff27fd2b..bea5eda8da 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -1334,7 +1334,7 @@ func TestAgreementFastRecoveryDownMiss(t *testing.T) { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer @@ -1435,7 +1435,7 @@ func TestAgreementFastRecoveryLate(t *testing.T) { } } - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer @@ -1548,7 +1548,7 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { } } - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer @@ -1589,7 +1589,7 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer @@ -1681,7 +1681,7 @@ func TestAgreementBlockReplayBug_b29ea57(t *testing.T) { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) } @@ -1690,7 +1690,7 @@ func TestAgreementBlockReplayBug_b29ea57(t *testing.T) { triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) } @@ -1743,7 +1743,7 @@ func TestAgreementLateCertBug(t *testing.T) { closeFn() baseNetwork.repairAll() - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) } @@ -1819,7 +1819,7 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) { } } - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) require.Equal(t, 4, int(zeroes)) } @@ -1846,7 +1846,7 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) { } } - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) require.Equal(t, 5, int(zeroes)) } @@ -1924,7 +1924,7 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) { } return params }) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) require.Equal(t, 4, int(zeroes)) } @@ -1950,7 +1950,7 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) { panic(errstr) } } - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) } @@ -2025,7 +2025,7 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { } // generate a bottom quorum; let only one node see it. baseNetwork.crown(0) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) if clocks[0].(*testingClock).zeroes != zeroes+1 { errstr := fmt.Sprintf("node 0 did not enter new period from bot quorum") panic(errstr) @@ -2043,11 +2043,11 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { activityMonitor.waitForQuiet() // actually create the value quorum - _, upper := (next).nextVoteRanges() + _, upper := (next).nextVoteRanges(DeadlineTimeout(0, version)) triggerGlobalTimeout(upper, TimeoutDeadline, clocks[1:], activityMonitor) // activates next timers zeroes = expectNoNewPeriod(clocks[1:], zeroes) - lower, upper := (next + 1).nextVoteRanges() + lower, upper := (next + 1).nextVoteRanges(DeadlineTimeout(0, version)) delta := time.Duration(testingRand{}.Uint64() % uint64(upper-lower)) triggerGlobalTimeout(lower+delta, TimeoutDeadline, clocks[1:], activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) @@ -2076,7 +2076,7 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { } } - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) } @@ -2190,7 +2190,7 @@ func TestAgreementSlowPayloadsPostDeadline(t *testing.T) { { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) zeroes = expectNoNewPeriod(clocks, zeroes) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) } @@ -2251,7 +2251,7 @@ func TestAgreementLargePeriods(t *testing.T) { zeroes = expectNoNewPeriod(clocks, zeroes) baseNetwork.repairAll() - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(period(p), version), TimeoutDeadline, clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) require.Equal(t, 4+p, int(zeroes)) } @@ -2363,7 +2363,7 @@ func TestAgreementRegression_WrongPeriodPayloadVerificationCancellation_8ba23942 // release proposed blocks in a controlled manner to prevent oversubscription of verification pocket1 := make(chan multicastParams, 100) closeFn = baseNetwork.pocketAllCompound(pocket1) - triggerGlobalTimeout(deadlineTimeout, TimeoutDeadline, clocks, activityMonitor) + triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) baseNetwork.repairAll() close(pocket1) { diff --git a/agreement/types.go b/agreement/types.go index 664fd3a2b0..000f03e1ea 100644 --- a/agreement/types.go +++ b/agreement/types.go @@ -50,7 +50,7 @@ type Deadline struct { Type TimeoutType } -var deadlineTimeout = config.Protocol.BigLambda + config.Protocol.SmallLambda +var defaultDeadlineTimeout = config.Protocol.BigLambda + config.Protocol.SmallLambda var partitionStep = next + 3 var recoveryExtraTimeout = config.Protocol.SmallLambda @@ -63,9 +63,17 @@ func FilterTimeout(p period, v protocol.ConsensusVersion) time.Duration { return config.Consensus[v].AgreementFilterTimeout } -// DeadlineTimeout is the duration of the second agreement step. -func DeadlineTimeout() time.Duration { - return deadlineTimeout +// DeadlineTimeout is the duration of the second agreement step, varying based on period and consensus version. +func DeadlineTimeout(p period, v protocol.ConsensusVersion) time.Duration { + if p == 0 { + return config.Consensus[v].AgreementDeadlineTimeoutPeriod0 + } + return defaultDeadlineTimeout +} + +// DefaultDeadlineTimeout is the default duration of the second agreement step. +func DefaultDeadlineTimeout() time.Duration { + return defaultDeadlineTimeout } type ( @@ -92,10 +100,10 @@ const ( down ) -func (s step) nextVoteRanges() (lower, upper time.Duration) { +func (s step) nextVoteRanges(deadlineTimeout time.Duration) (lower, upper time.Duration) { extra := recoveryExtraTimeout // eg 2000 ms - lower = deadlineTimeout // eg 17000 ms (15000 + 2000) - upper = lower + extra // eg 19000 ms + lower = deadlineTimeout // based on types.DeadlineTimeout() + upper = lower + extra for i := next; i < s; i++ { extra *= 2 diff --git a/catchup/service.go b/catchup/service.go index bcf204b134..01ff1678ef 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -91,7 +91,7 @@ type Service struct { net network.GossipNode auth BlockAuthenticator parallelBlocks uint64 - deadlineTimeout time.Duration + roundTimeEstimate time.Duration prevBlockFetchTime time.Time blockValidationPool execpool.BacklogPool @@ -146,7 +146,7 @@ func MakeService(log logging.Logger, config config.Local, net network.GossipNode s.unmatchedPendingCertificates = unmatchedPendingCertificates s.log = log.With("Context", "sync") s.parallelBlocks = config.CatchupParallelBlocks - s.deadlineTimeout = agreement.DeadlineTimeout() + s.roundTimeEstimate = agreement.DefaultDeadlineTimeout() s.blockValidationPool = blockValidationPool s.syncNow = make(chan struct{}, 1) @@ -556,11 +556,11 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { // if ledger is busy, pause for some time to let the fetchAndWrite goroutines to finish fetching in-flight blocks. start := time.Now() - for (s.ledger.IsWritingCatchpointDataFile() || s.ledger.IsBehindCommittingDeltas()) && time.Since(start) < s.deadlineTimeout { + for (s.ledger.IsWritingCatchpointDataFile() || s.ledger.IsBehindCommittingDeltas()) && time.Since(start) < s.roundTimeEstimate { time.Sleep(100 * time.Millisecond) } - // if ledger is still busy after s.deadlineTimeout timeout then abort the current pipelinedFetch invocation. + // if ledger is still busy after s.roundTimeEstimate timeout then abort the current pipelinedFetch invocation. // if we're writing a catchpoint file, stop catching up to reduce the memory pressure. Once we finish writing the file we // could resume with the catchup. @@ -616,7 +616,7 @@ func (s *Service) periodicSync() { s.sync() } stuckInARow := 0 - sleepDuration := s.deadlineTimeout + sleepDuration := s.roundTimeEstimate for { currBlock := s.ledger.LastRound() select { @@ -627,7 +627,7 @@ func (s *Service) periodicSync() { stuckInARow = 0 // go to sleep for a short while, for a random duration. // we want to sleep for a random duration since it would "de-syncronize" us from the ledger advance sync - sleepDuration = time.Duration(crypto.RandUint63()) % s.deadlineTimeout + sleepDuration = time.Duration(crypto.RandUint63()) % s.roundTimeEstimate continue case <-s.syncNow: if s.parallelBlocks == 0 || s.ledger.IsWritingCatchpointDataFile() || s.ledger.IsBehindCommittingDeltas() { @@ -637,8 +637,8 @@ func (s *Service) periodicSync() { s.log.Info("Immediate resync triggered; resyncing") s.sync() case <-time.After(sleepDuration): - if sleepDuration < s.deadlineTimeout || s.cfg.DisableNetworking { - sleepDuration = s.deadlineTimeout + if sleepDuration < s.roundTimeEstimate || s.cfg.DisableNetworking { + sleepDuration = s.roundTimeEstimate continue } // if the catchup is disabled in the config file, just skip it. diff --git a/catchup/service_test.go b/catchup/service_test.go index fc0ae38e1d..0c4cb5cc6b 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -237,7 +237,7 @@ func TestSyncRound(t *testing.T) { localCfg := config.GetDefaultLocal() s := MakeService(logging.Base(), localCfg, net, local, auth, nil, nil) s.log = &periodicSyncLogger{Logger: logging.Base()} - s.deadlineTimeout = 2 * time.Second + s.roundTimeEstimate = 2 * time.Second // Set disable round success err = s.SetDisableSyncRound(3) @@ -246,14 +246,14 @@ func TestSyncRound(t *testing.T) { s.Start() defer s.Stop() // wait past the initial sync - which is known to fail due to the above "auth" - time.Sleep(s.deadlineTimeout*2 - 200*time.Millisecond) + time.Sleep(s.roundTimeEstimate*2 - 200*time.Millisecond) require.Equal(t, initialLocalRound, local.LastRound()) auth.alter(-1, false) // wait until the catchup is done. Since we've might have missed the sleep window, we need to wait // until the synchronization is complete. waitStart := time.Now() - for time.Since(waitStart) < 2*s.deadlineTimeout { + for time.Since(waitStart) < 2*s.roundTimeEstimate { if remote.LastRound() == local.LastRound() { break } @@ -276,7 +276,7 @@ func TestSyncRound(t *testing.T) { s.UnsetDisableSyncRound() // wait until the catchup is done waitStart = time.Now() - for time.Since(waitStart) < 8*s.deadlineTimeout { + for time.Since(waitStart) < 8*s.roundTimeEstimate { if remote.LastRound() == local.LastRound() { break } @@ -326,19 +326,19 @@ func TestPeriodicSync(t *testing.T) { // Make Service s := MakeService(logging.Base(), defaultConfig, net, local, auth, nil, nil) s.log = &periodicSyncLogger{Logger: logging.Base()} - s.deadlineTimeout = 2 * time.Second + s.roundTimeEstimate = 2 * time.Second s.Start() defer s.Stop() // wait past the initial sync - which is known to fail due to the above "auth" - time.Sleep(s.deadlineTimeout*2 - 200*time.Millisecond) + time.Sleep(s.roundTimeEstimate*2 - 200*time.Millisecond) require.Equal(t, initialLocalRound, local.LastRound()) auth.alter(-1, false) // wait until the catchup is done. Since we've might have missed the sleep window, we need to wait // until the synchronization is complete. waitStart := time.Now() - for time.Since(waitStart) < 10*s.deadlineTimeout { + for time.Since(waitStart) < 10*s.roundTimeEstimate { if remote.LastRound() == local.LastRound() { break } @@ -717,7 +717,7 @@ func helperTestOnSwitchToUnSupportedProtocol( // Make Service s := MakeService(logging.Base(), config, net, local, &mockedAuthenticator{errorRound: -1}, nil, nil) - s.deadlineTimeout = 2 * time.Second + s.roundTimeEstimate = 2 * time.Second s.Start() defer s.Stop() @@ -1198,7 +1198,7 @@ func TestServiceLedgerUnavailable(t *testing.T) { cfg.CatchupParallelBlocks = 2 s := MakeService(logging.Base(), cfg, net, local, auth, nil, nil) s.log = &periodicSyncLogger{Logger: logging.Base()} - s.deadlineTimeout = 2 * time.Second + s.roundTimeEstimate = 2 * time.Second s.testStart() defer s.Stop() @@ -1245,7 +1245,7 @@ func TestServiceNoBlockForRound(t *testing.T) { s := MakeService(logging.Base(), cfg, net, local, auth, nil, nil) pl := &periodicSyncDebugLogger{periodicSyncLogger: periodicSyncLogger{Logger: logging.Base()}} s.log = pl - s.deadlineTimeout = 1 * time.Second + s.roundTimeEstimate = 1 * time.Second s.testStart() defer s.Stop() diff --git a/config/consensus.go b/config/consensus.go index a2f28b97d5..95004e91f9 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -162,6 +162,8 @@ type ConsensusParams struct { // time for nodes to wait for block proposal headers for period = 0, value should be configured to suit best case // critical path AgreementFilterTimeoutPeriod0 time.Duration + // Duration of the second agreement step for period=0, value should be configured to suit best case critical path + AgreementDeadlineTimeoutPeriod0 time.Duration FastRecoveryLambda time.Duration // time between fast recovery attempts @@ -848,8 +850,9 @@ func initConsensusProtocols() { DownCommitteeSize: 10000, DownCommitteeThreshold: 7750, - AgreementFilterTimeout: 4 * time.Second, - AgreementFilterTimeoutPeriod0: 4 * time.Second, + AgreementFilterTimeout: 4 * time.Second, + AgreementFilterTimeoutPeriod0: 4 * time.Second, + AgreementDeadlineTimeoutPeriod0: Protocol.BigLambda + Protocol.SmallLambda, FastRecoveryLambda: 5 * time.Minute, @@ -1389,6 +1392,8 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here vFuture.EnableLogicSigCostPooling = true + vFuture.AgreementDeadlineTimeoutPeriod0 = 4 * time.Second + vFuture.StateProofBlockHashInLightHeader = true // Setting DynamicFilterTimeout in vFuture will impact e2e test performance diff --git a/daemon/algod/api/server/common/handlers.go b/daemon/algod/api/server/common/handlers.go index 95a1dbcce1..938d3ee102 100644 --- a/daemon/algod/api/server/common/handlers.go +++ b/daemon/algod/api/server/common/handlers.go @@ -121,14 +121,14 @@ func Ready(ctx lib.ReqContext, context echo.Context) { // must satisfy following sub conditions: // 1. the node is not in a fast-catchup stage // 2. the node's time since last round should be [0, deadline), - // while deadline = bigLambda + smallLambda = 17s + // while deadline = agreement.DefaultDeadlineTimeout = 17s // 3. the node's catchup time is 0 isReadyFromStat := func(status node.StatusReport) bool { timeSinceLastRound := status.TimeSinceLastRound().Milliseconds() return len(status.Catchpoint) == 0 && timeSinceLastRound >= 0 && - timeSinceLastRound < agreement.DeadlineTimeout().Milliseconds() && + timeSinceLastRound < agreement.DefaultDeadlineTimeout().Milliseconds() && status.CatchupTime.Milliseconds() == 0 } From 9229066ea56b1f8713385b905006e8ba09265cef Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:51:02 -0500 Subject: [PATCH 035/218] network: fixes to public address support (#5851) * Remove http.Request.RemoteAddr overwriting in request tracker * Remove http.Request from request tracker * Add a new remoteAddresss() method providing most meaningful address for incoming requests --- network/requestTracker.go | 78 +++++++++++++---- network/requestTracker_test.go | 26 ++++++ network/wsNetwork.go | 26 +++--- network/wsNetwork_test.go | 151 ++++++++++++++++++--------------- network/wsPeer.go | 1 + 5 files changed, 185 insertions(+), 97 deletions(-) diff --git a/network/requestTracker.go b/network/requestTracker.go index 6445b93497..63fd4a72b6 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -40,15 +40,27 @@ const ( ) // TrackerRequest hold the tracking data associated with a single request. +// It supposed by an upstream http.Handler called before the wsNetwork's ServeHTTP +// and wsNetwork's Listener (see Accept() method) type TrackerRequest struct { - created time.Time - remoteHost string - remotePort string - remoteAddr string - request *http.Request + created time.Time + // remoteHost is IP address of the remote host and it is equal to either + // a host part of the remoteAddr or to the value of X-Forwarded-For header (UseXForwardedForAddressField config value). + remoteHost string + // remotePort is the port of the remote peer as reported by the connection or + // by the standard http.Request.RemoteAddr field. + remotePort string + // remoteAddr is IP:Port of the remote host retrieved from the connection + // or from the standard http.Request.RemoteAddr field. + // This field is the real address of the remote incoming connection. + remoteAddr string + // otherPublicAddr is the public address of the other node, as reported by the other node + // via the X-Algorand-Location header. + // It is used for logging and as a rootURL for when creating a new wsPeer from a request. + otherPublicAddr string + otherTelemetryGUID string otherInstanceName string - otherPublicAddr string connection net.Conn noPrune bool } @@ -68,6 +80,43 @@ func makeTrackerRequest(remoteAddr, remoteHost, remotePort string, createTime ti } } +// remoteAddress a best guessed remote address for the request. +// Rational is the following: +// remoteAddress() is used either for logging or as rootURL for creating a new wsPeer. +// rootURL is an address to connect to. It is well defined only for peers from a phonebooks, +// and for incoming peers the best guess is either otherPublicAddr, remoteHost, or remoteAddr. +// - otherPublicAddr is provided by a remote peer by X-Algorand-Location header and cannot be trusted, +// but can be used if remoteHost matches to otherPublicAddr value. In this case otherPublicAddr is a better guess +// for a rootURL because it might include a port. +// - remoteHost is either a real address of the remote peer or a value of X-Forwarded-For header. +// Use it if remoteHost was taken from X-Forwarded-For header. +// Note, the remoteHost does not include a port since a listening port is not known. +// - remoteAddr is used otherwise. +func (tr *TrackerRequest) remoteAddress() string { + if len(tr.otherPublicAddr) != 0 { + url, err := ParseHostOrURL(tr.otherPublicAddr) + if err == nil && len(tr.remoteHost) > 0 && url.Hostname() == tr.remoteHost { + return tr.otherPublicAddr + } + } + url, err := ParseHostOrURL(tr.remoteAddr) + if err != nil { + // tr.remoteAddr can't be parsed so try to use tr.remoteHost + // there is a chance it came from a proxy and has a meaningful value + if len(tr.remoteHost) != 0 { + return tr.remoteHost + } + // otherwise fallback to tr.remoteAddr + return tr.remoteAddr + } + if url.Hostname() != tr.remoteHost { + // if remoteAddr's host not equal to remoteHost then the remoteHost + // is definitely came from a proxy, use it + return tr.remoteHost + } + return tr.remoteAddr +} + // hostIncomingRequests holds all the requests that are originating from a single host. type hostIncomingRequests struct { remoteHost string @@ -142,7 +191,6 @@ func (ard *hostIncomingRequests) add(trackerRequest *TrackerRequest) { } // it's going to be added somewhere in the middle. ard.requests = append(ard.requests[:itemIdx], append([]*TrackerRequest{trackerRequest}, ard.requests[itemIdx:]...)...) - return } // countConnections counts the number of connection that we have that occurred after the provided specified time @@ -372,7 +420,7 @@ func (rt *RequestTracker) sendBlockedConnectionResponse(conn net.Conn, requestTi func (rt *RequestTracker) pruneAcceptedConnections(pruneStartDate time.Time) { localAddrToRemove := []net.Addr{} for localAddr, request := range rt.acceptedConnections { - if request.noPrune == false && request.created.Before(pruneStartDate) { + if !request.noPrune && request.created.Before(pruneStartDate) { localAddrToRemove = append(localAddrToRemove, localAddr) } } @@ -397,7 +445,7 @@ func (rt *RequestTracker) getWaitUntilNoConnectionsChannel(checkInterval time.Du return len(rt.httpConnections) == 0 } - for true { + for { if checkEmpty(rt) { close(done) return @@ -449,7 +497,7 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http. trackedRequest := rt.acceptedConnections[localAddr] if trackedRequest != nil { // update the original tracker request so that it won't get pruned. - if trackedRequest.noPrune == false { + if !trackedRequest.noPrune { trackedRequest.noPrune = true rt.hostRequests.convertToAdditionalRequest(trackedRequest) } @@ -464,10 +512,9 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http. } // update the origin address. - rt.updateRequestRemoteAddr(trackedRequest, request) + rt.remoteHostProxyFix(request.Header, trackedRequest) rt.httpConnectionsMu.Lock() - trackedRequest.request = request trackedRequest.otherTelemetryGUID, trackedRequest.otherInstanceName, trackedRequest.otherPublicAddr = getCommonHeaders(request.Header) rt.httpHostRequests.addRequest(trackedRequest) rt.httpHostRequests.pruneRequests(rateLimitingWindowStartTime) @@ -506,13 +553,12 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http. } -// updateRequestRemoteAddr updates the origin IP address in both the trackedRequest as well as in the request.RemoteAddr string -func (rt *RequestTracker) updateRequestRemoteAddr(trackedRequest *TrackerRequest, request *http.Request) { - originIP := rt.getForwardedConnectionAddress(request.Header) +// remoteHostProxyFix updates the origin IP address in the trackedRequest +func (rt *RequestTracker) remoteHostProxyFix(header http.Header, trackedRequest *TrackerRequest) { + originIP := rt.getForwardedConnectionAddress(header) if originIP == nil { return } - request.RemoteAddr = originIP.String() + ":" + trackedRequest.remotePort trackedRequest.remoteHost = originIP.String() } diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index 1941069147..65349987ea 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -172,6 +172,32 @@ func TestRateLimiting(t *testing.T) { } } +func TestRemoteAddress(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tr := makeTrackerRequest("127.0.0.1:444", "", "", time.Now(), nil) + require.Equal(t, "127.0.0.1:444", tr.remoteAddr) + require.Equal(t, "127.0.0.1", tr.remoteHost) + require.Equal(t, "444", tr.remotePort) + + require.Equal(t, "127.0.0.1:444", tr.remoteAddress()) + + // remoteHost set to something else via X-Forwared-For HTTP headers + tr.remoteHost = "10.0.0.1" + require.Equal(t, "10.0.0.1", tr.remoteAddress()) + + // otherPublicAddr is set via X-Algorand-Location HTTP header + // and matches to the remoteHost + tr.otherPublicAddr = "10.0.0.1:555" + require.Equal(t, "10.0.0.1:555", tr.remoteAddress()) + + // otherPublicAddr does not match remoteHost + tr.remoteHost = "127.0.0.1" + tr.otherPublicAddr = "127.0.0.99:555" + require.Equal(t, "127.0.0.1:444", tr.remoteAddress()) +} + func TestIsLocalHost(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 7f8b3046c9..d316fcd814 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -377,9 +377,9 @@ func (wn *WebsocketNetwork) PublicAddress() string { // If except is not nil then we will not send it to that neighboring Peer. // if wait is true then the call blocks until the packet has actually been sent to all neighbors. func (wn *WebsocketNetwork) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error { - dataArray := make([][]byte, 1, 1) + dataArray := make([][]byte, 1) dataArray[0] = data - tagArray := make([]protocol.Tag, 1, 1) + tagArray := make([]protocol.Tag, 1) tagArray[0] = tag return wn.broadcaster.BroadcastArray(ctx, tagArray, dataArray, wait, except) } @@ -947,7 +947,7 @@ func (wn *WebsocketNetwork) checkProtocolVersionMatch(otherHeaders http.Header) // checkIncomingConnectionVariables checks the variables that were provided on the request, and compares them to the // local server supported parameters. If all good, it returns http.StatusOK; otherwise, it write the error to the ResponseWriter // and returns the http status. -func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.ResponseWriter, request *http.Request) int { +func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.ResponseWriter, request *http.Request, remoteAddrForLogging string) int { // check to see that the genesisID in the request URI is valid and matches the supported one. pathVars := mux.Vars(request) otherGenesisID, hasGenesisID := pathVars["genesisID"] @@ -958,7 +958,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo } if wn.GenesisID != otherGenesisID { - wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", request.RemoteAddr, wn.GenesisID, otherGenesisID, request.Header))) + wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", remoteAddrForLogging, wn.GenesisID, otherGenesisID, request.Header))) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "mismatching genesis-id"}) response.WriteHeader(http.StatusPreconditionFailed) n, err := response.Write([]byte("mismatching genesis ID")) @@ -973,7 +973,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo // This is pretty harmless and some configurations of phonebooks or DNS records make this likely. Quietly filter it out. var message string // missing header. - wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", request.RemoteAddr, wn.RandomID, request.Header))) + wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", remoteAddrForLogging, wn.RandomID, request.Header))) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "missing random ID header"}) message = fmt.Sprintf("Request was missing a %s header", NodeRandomHeader) response.WriteHeader(http.StatusPreconditionFailed) @@ -985,7 +985,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo } else if otherRandom == wn.RandomID { // This is pretty harmless and some configurations of phonebooks or DNS records make this likely. Quietly filter it out. var message string - wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", request.RemoteAddr, wn.RandomID) + wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", remoteAddrForLogging, wn.RandomID) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "matching random ID header"}) message = fmt.Sprintf("Request included matching %s=%s header", NodeRandomHeader, otherRandom) response.WriteHeader(http.StatusLoopDetected) @@ -1025,7 +1025,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt matchingVersion, otherVersion := wn.checkProtocolVersionMatch(request.Header) if matchingVersion == "" { - wn.log.Info(filterASCII(fmt.Sprintf("new peer %s version mismatch, mine=%v theirs=%s, headers %#v", request.RemoteAddr, wn.supportedProtocolVersions, otherVersion, request.Header))) + wn.log.Info(filterASCII(fmt.Sprintf("new peer %s version mismatch, mine=%v theirs=%s, headers %#v", trackedRequest.remoteHost, wn.supportedProtocolVersions, otherVersion, request.Header))) networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "mismatching protocol version"}) response.WriteHeader(http.StatusPreconditionFailed) message := fmt.Sprintf("Requested version %s not in %v mismatches server version", filterASCII(otherVersion), wn.supportedProtocolVersions) @@ -1036,14 +1036,11 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt return } - if wn.checkIncomingConnectionVariables(response, request) != http.StatusOK { + if wn.checkIncomingConnectionVariables(response, request, trackedRequest.remoteAddress()) != http.StatusOK { // we've already logged and written all response(s). return } - // if UseXForwardedForAddressField is not empty, attempt to override the otherPublicAddr with the X Forwarded For origin - trackedRequest.otherPublicAddr = trackedRequest.remoteAddr - responseHeader := make(http.Header) wn.setHeaders(responseHeader) responseHeader.Set(ProtocolVersionHeader, matchingVersion) @@ -1063,7 +1060,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt peerIDChallenge, peerID, err = wn.identityScheme.VerifyRequestAndAttachResponse(responseHeader, request.Header) if err != nil { networkPeerIdentityError.Inc(nil) - wn.log.With("err", err).With("remote", trackedRequest.otherPublicAddr).With("local", localAddr).Warnf("peer (%s) supplied an invalid identity challenge, abandoning peering", trackedRequest.otherPublicAddr) + wn.log.With("err", err).With("remote", trackedRequest.remoteAddress()).With("local", localAddr).Warnf("peer (%s) supplied an invalid identity challenge, abandoning peering", trackedRequest.remoteAddr) return } } @@ -1081,7 +1078,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt } peer := &wsPeer{ - wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.otherPublicAddr, wn.GetRoundTripper(), trackedRequest.remoteHost), + wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.remoteAddress(), wn.GetRoundTripper(), trackedRequest.remoteHost), conn: wsPeerWebsocketConnImpl{conn}, outgoing: false, InstanceName: trackedRequest.otherInstanceName, @@ -1097,7 +1094,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt peer.TelemetryGUID = trackedRequest.otherTelemetryGUID peer.init(wn.config, wn.outgoingMessagesBufferSize) wn.addPeer(peer) - wn.log.With("event", "ConnectedIn").With("remote", trackedRequest.otherPublicAddr).With("local", localAddr).Infof("Accepted incoming connection from peer %s", trackedRequest.otherPublicAddr) + wn.log.With("event", "ConnectedIn").With("remote", trackedRequest.remoteAddress()).With("local", localAddr).Infof("Accepted incoming connection from peer %s", trackedRequest.remoteAddr) wn.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerEvent, telemetryspec.PeerEventDetails{ Address: trackedRequest.remoteHost, @@ -2047,6 +2044,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { } }() defer wn.wg.Done() + requestHeader := make(http.Header) wn.setHeaders(requestHeader) for _, supportedProtocolVersion := range wn.supportedProtocolVersions { diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 05e484843a..f8eeeb71f4 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1332,8 +1332,6 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { addrA, ok := netA.Address() require.True(t, ok) - gossipA, err := netA.addrToGossipAddr(addrA) - require.NoError(t, err) addrB, ok := netB.Address() require.True(t, ok) @@ -1349,7 +1347,9 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { netA.wg.Add(1) netA.tryConnect(addrB, gossipB) // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return len(netA.GetPeers(PeersConnectedOut)) == 1 + }, time.Second, 50*time.Millisecond) } // just one A->B connection assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) @@ -1362,17 +1362,16 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { assert.Equal(t, 1, netA.identityTracker.(*mockIdentityTracker).getInsertCount()) // netB has to wait for a final verification message over WS Handler, so pause a moment - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return netB.identityTracker.(*mockIdentityTracker).getSetCount() == 1 + }, time.Second, 50*time.Millisecond) + assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getInsertCount()) // bi-directional connection from B should not proceed - if _, ok := netB.tryConnectReserveAddr(addrA); ok { - netB.wg.Add(1) - netB.tryConnect(addrA, gossipA) - // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) - } + _, ok = netB.tryConnectReserveAddr(addrA) + assert.False(t, ok) // still just one A->B connection assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) @@ -1381,9 +1380,9 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { assert.Equal(t, 0, len(netB.GetPeers(PeersConnectedOut))) // netA never attempts to set identity as it never sees a verified identity assert.Equal(t, 1, netA.identityTracker.(*mockIdentityTracker).getSetCount()) - // netB would attempt to add the identity to the tracker - // but it would not end up being added - assert.Equal(t, 2, netB.identityTracker.(*mockIdentityTracker).getSetCount()) + // no connecton => netB does attepmt to add the identity to the tracker + // and it would not end up being added + assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getInsertCount()) // Check deduplication again, this time from A @@ -1391,15 +1390,19 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { // will prevent this connection from attempting in the first place // in the real world, that isConnectedTo doesn't always trigger, if the hosts are behind // a load balancer or other NAT - if _, ok := netA.tryConnectReserveAddr(addrB); ok || true { - netA.wg.Add(1) - netA.tryConnect(addrB, gossipB) - // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) - } + _, ok = netA.tryConnectReserveAddr(addrB) + assert.False(t, ok) + netA.wg.Add(1) + old := networkPeerIdentityDisconnect.GetUint64Value() + netA.tryConnect(addrB, gossipB) + // let the tryConnect go forward + assert.Eventually(t, func() bool { + new := networkPeerIdentityDisconnect.GetUint64Value() + return new > old + }, time.Second, 50*time.Millisecond) // netB never tries to add a new identity, since the connection gets abandoned before it is verified - assert.Equal(t, 2, netB.identityTracker.(*mockIdentityTracker).getSetCount()) + assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getInsertCount()) // still just one A->B connection assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) @@ -1411,11 +1414,9 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { // the underlying connection is being closed. In this case, the read loop // on the peer will detect and close the peer. Since this is asynchronous, // we wait and check regularly to allow the connection to settle - assert.Eventually( - t, - func() bool { return len(netB.GetPeers(PeersConnectedIn)) == 1 }, - 5*time.Second, - 100*time.Millisecond) + assert.Eventually(t, func() bool { + return len(netB.GetPeers(PeersConnectedIn)) == 1 + }, time.Second, 50*time.Millisecond) // Now have A connect to node C, which has the same PublicAddress as B (e.g., because it shares the // same public load balancer endpoint). C will have a different identity keypair and so will not be @@ -1432,13 +1433,15 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { require.True(t, ok) gossipC, err := netC.addrToGossipAddr(addrC) require.NoError(t, err) - addrC = hostAndPort(addrC) + assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut))) // A connects to C (but uses addrB here to simulate case where B & C have the same PublicAddress) netA.wg.Add(1) netA.tryConnect(addrB, gossipC) // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return len(netA.GetPeers(PeersConnectedOut)) == 2 + }, time.Second, 50*time.Millisecond) // A->B and A->C both open assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) @@ -1453,7 +1456,10 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { assert.Equal(t, 2, netA.identityTracker.(*mockIdentityTracker).getInsertCount()) // netC has to wait for a final verification message over WS Handler, so pause a moment - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return netC.identityTracker.(*mockIdentityTracker).getSetCount() == 1 + }, time.Second, 50*time.Millisecond) + assert.Equal(t, 1, netC.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 1, netC.identityTracker.(*mockIdentityTracker).getInsertCount()) @@ -1481,8 +1487,6 @@ func TestPeeringSenderIdentityChallengeOnly(t *testing.T) { addrA, ok := netA.Address() require.True(t, ok) - gossipA, err := netA.addrToGossipAddr(addrA) - require.NoError(t, err) addrB, ok := netB.Address() require.True(t, ok) @@ -1493,12 +1497,16 @@ func TestPeeringSenderIdentityChallengeOnly(t *testing.T) { addrA = hostAndPort(addrA) addrB = hostAndPort(addrB) + assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut))) + assert.Equal(t, 0, len(netB.GetPeers(PeersConnectedIn))) + // first connection should work just fine if _, ok := netA.tryConnectReserveAddr(addrB); ok { netA.wg.Add(1) netA.tryConnect(addrB, gossipB) - // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return len(netA.GetPeers(PeersConnectedOut)) == 1 + }, time.Second, 50*time.Millisecond) } assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut))) assert.Equal(t, 1, len(netB.GetPeers(PeersConnectedIn))) @@ -1507,18 +1515,15 @@ func TestPeeringSenderIdentityChallengeOnly(t *testing.T) { assert.Equal(t, 0, netA.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount()) - // bi-directional connection should also work - if _, ok := netB.tryConnectReserveAddr(addrA); ok { - netB.wg.Add(1) - netB.tryConnect(addrA, gossipA) - // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) - } - // the nodes are connected redundantly - assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedIn))) + // bi-directional connection does not work because netA advertises its public address + _, ok = netB.tryConnectReserveAddr(addrA) + assert.False(t, ok) + + // no redundant connections + assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut))) assert.Equal(t, 1, len(netB.GetPeers(PeersConnectedIn))) - assert.Equal(t, 1, len(netB.GetPeers(PeersConnectedOut))) + assert.Equal(t, 0, len(netB.GetPeers(PeersConnectedOut))) // confirm identity map was not added to for either host assert.Equal(t, 0, netA.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount()) @@ -1558,12 +1563,15 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) { addrA = hostAndPort(addrA) addrB = hostAndPort(addrB) + assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut))) // first connection should work just fine if _, ok := netA.tryConnectReserveAddr(addrB); ok { netA.wg.Add(1) netA.tryConnect(addrB, gossipB) // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return len(netA.GetPeers(PeersConnectedOut)) == 1 + }, time.Second, 50*time.Millisecond) } // single A->B connection assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) @@ -1580,7 +1588,9 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) { netB.wg.Add(1) netB.tryConnect(addrA, gossipA) // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return len(netB.GetPeers(PeersConnectedOut)) == 1 + }, time.Second, 50*time.Millisecond) } assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedIn))) assert.Equal(t, 1, len(netA.GetPeers(PeersConnectedOut))) @@ -1591,7 +1601,7 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) { assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount()) } -// TestPeeringIncorrectDeduplicationName confirm that if the reciever can't match +// TestPeeringIncorrectDeduplicationName confirm that if the reciever can't match // the Address in the challenge to its PublicAddress, identities aren't exchanged, but peering continues func TestPeeringIncorrectDeduplicationName(t *testing.T) { partitiontest.PartitionTest(t) @@ -1625,12 +1635,15 @@ func TestPeeringIncorrectDeduplicationName(t *testing.T) { addrA = hostAndPort(addrA) addrB = hostAndPort(addrB) + assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedOut))) // first connection should work just fine if _, ok := netA.tryConnectReserveAddr(addrB); ok { netA.wg.Add(1) netA.tryConnect(addrB, gossipB) // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) + assert.Eventually(t, func() bool { + return len(netA.GetPeers(PeersConnectedOut)) == 1 + }, time.Second, 50*time.Millisecond) } // single A->B connection assert.Equal(t, 0, len(netA.GetPeers(PeersConnectedIn))) @@ -1643,14 +1656,18 @@ func TestPeeringIncorrectDeduplicationName(t *testing.T) { assert.Equal(t, 0, netA.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount()) - // bi-directional connection should also work + // bi-directional connection would now work since netB detects to be connected to netA in tryConnectReserveAddr, + // so force it. // this second connection should set identities, because the reciever address matches now - if _, ok := netB.tryConnectReserveAddr(addrA); ok { - netB.wg.Add(1) - netB.tryConnect(addrA, gossipA) - // let the tryConnect go forward - time.Sleep(250 * time.Millisecond) - } + _, ok = netB.tryConnectReserveAddr(addrA) + assert.False(t, ok) + netB.wg.Add(1) + netB.tryConnect(addrA, gossipA) + // let the tryConnect go forward + assert.Eventually(t, func() bool { + return len(netB.GetPeers(PeersConnectedOut)) == 1 + }, time.Second, 50*time.Millisecond) + // confirm that at this point the identityTracker was called once per network // and inserted once per network assert.Equal(t, 1, netA.identityTracker.(*mockIdentityTracker).getSetCount()) @@ -1982,14 +1999,13 @@ func TestPeeringWithBadIdentityVerification(t *testing.T) { partitiontest.PartitionTest(t) type testCase struct { - name string - verifyResponse func(t *testing.T, h http.Header, c identityChallengeValue) (crypto.PublicKey, []byte, error) - totalInA int - totalOutA int - totalInB int - totalOutB int - additionalSleep time.Duration - occupied bool + name string + verifyResponse func(t *testing.T, h http.Header, c identityChallengeValue) (crypto.PublicKey, []byte, error) + totalInA int + totalOutA int + totalInB int + totalOutB int + occupied bool } testCases := []testCase{ @@ -2602,7 +2618,7 @@ func TestSlowPeerDisconnection(t *testing.T) { peers, _ = netA.peerSnapshot(peers) if len(peers) == 0 || peers[0] != peer { // make sure it took more than 1 second, and less than 5 seconds. - waitTime := time.Now().Sub(beforeLoopTime) + waitTime := time.Since(beforeLoopTime) require.LessOrEqual(t, int64(time.Second), int64(waitTime)) require.GreaterOrEqual(t, int64(5*time.Second), int64(waitTime)) break @@ -2895,7 +2911,7 @@ func TestWebsocketNetworkMessageOfInterest(t *testing.T) { netB.config.EnablePingHandler = false addrA, postListen := netA.Address() require.True(t, postListen) - t.Log(addrA) + t.Logf("netA %s", addrA) netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) // have netB asking netA to send it ft2, deregister ping handler to make sure that we aren't exceeding the maximum MOI messagesize @@ -2905,6 +2921,8 @@ func TestWebsocketNetworkMessageOfInterest(t *testing.T) { netB.Start() defer netStop(t, netB, "B") + addrB, _ := netB.Address() + t.Logf("netB %s", addrB) incomingMsgSync := deadlock.Mutex{} msgCounters := make(map[protocol.Tag]int) @@ -3652,7 +3670,7 @@ func BenchmarkVariableTransactionMessageBlockSizes(t *testing.B) { netB.Broadcast(context.Background(), protocol.TxnTag, dataBuffer, true, nil) <-msgProcessed } - deltaTime := time.Now().Sub(startTime) + deltaTime := time.Since(startTime) rate = float64(t.N) * float64(time.Second) / float64(deltaTime) t.ReportMetric(rate, "txn/sec") }) @@ -3794,7 +3812,6 @@ func TestWebsocketNetworkTelemetryTCP(t *testing.T) { type mockServer struct { *httptest.Server URL string - t *testing.T waitForClientClose bool } @@ -3845,7 +3862,7 @@ func (t mockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } wr.Close() - for true { + for { // echo a message back to the client _, _, err := ws.NextReader() if err != nil { @@ -4002,7 +4019,7 @@ func TestDiscardUnrequestedBlockResponse(t *testing.T) { require.Eventually(t, func() bool { return netA.NumPeers() == 1 }, 500*time.Millisecond, 25*time.Millisecond) // send an unrequested block response - msg := make([]sendMessage, 1, 1) + msg := make([]sendMessage, 1) msg[0] = sendMessage{ data: append([]byte(protocol.TopicMsgRespTag), []byte("foo")...), enqueued: time.Now(), diff --git a/network/wsPeer.go b/network/wsPeer.go index 4ed32539a7..7cbdbeaebc 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -360,6 +360,7 @@ func makePeerCore(ctx context.Context, net GossipNode, log logging.Logger, readB } // GetAddress returns the root url to use to connect to this peer. +// This implements HTTPPeer interface and used by external services to determine where to connect to. // TODO: should GetAddress be added to Peer interface? func (wp *wsPeerCore) GetAddress() string { return wp.rootURL From b37fadbd1b14e9e1455b6eb9ecb27535a094c20a Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 7 Dec 2023 18:11:46 -0500 Subject: [PATCH 036/218] AVM: Add `global GenesisHash` (#5858) --- cmd/tealdbg/localLedger.go | 4 ++ daemon/algod/api/server/v2/dryrun.go | 5 ++ data/transactions/logic/README.md | 1 + data/transactions/logic/TEAL_opcodes_v10.md | 1 + data/transactions/logic/assembler_test.go | 18 +++--- data/transactions/logic/eval.go | 23 ++++++-- data/transactions/logic/evalAppTxn_test.go | 31 +++++++++++ data/transactions/logic/evalStateful_test.go | 10 +++- data/transactions/logic/fields.go | 5 ++ data/transactions/logic/fields_string.go | 7 ++- data/transactions/logic/fields_test.go | 58 ++++++++++++++++++++ data/transactions/logic/langspec_v10.json | 6 +- data/transactions/logic/ledger_test.go | 8 +++ data/transactions/logic/teal.tmLanguage.json | 2 +- data/transactions/verify/txn_test.go | 3 + ledger/eval/appcow_test.go | 5 +- ledger/eval/cow.go | 6 ++ ledger/eval/cow_test.go | 5 +- ledger/eval/eval.go | 5 ++ ledger/eval/eval_test.go | 4 ++ 20 files changed, 184 insertions(+), 23 deletions(-) diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go index 64dfe7329d..fc7655173f 100644 --- a/cmd/tealdbg/localLedger.go +++ b/cmd/tealdbg/localLedger.go @@ -281,6 +281,10 @@ func (l *localLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) { return bookkeeping.BlockHeader{}, nil } +func (l *localLedger) GenesisHash() crypto.Digest { + return crypto.Digest{} +} + func (l *localLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { return nil, fmt.Errorf("localLedger: GetStateProofVerificationContext, needed for state proof verification, is not implemented in debugger") } diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index ef0de80850..acf5fc94b7 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" "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/bookkeeping" @@ -241,6 +242,10 @@ func (dl *dryrunLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) return bookkeeping.BlockHeader{}, nil } +func (dl *dryrunLedger) GenesisHash() crypto.Digest { + return crypto.Digest{} +} + func (dl *dryrunLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { return nil } diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index da4fdefac9..a38ba1462e 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -633,6 +633,7 @@ Global fields are fields that are common to all the transactions in the group. I | 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. | | 15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. | | 16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. | +| 17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. | **Asset Fields** diff --git a/data/transactions/logic/TEAL_opcodes_v10.md b/data/transactions/logic/TEAL_opcodes_v10.md index 7df81c33a8..81d742fbd4 100644 --- a/data/transactions/logic/TEAL_opcodes_v10.md +++ b/data/transactions/logic/TEAL_opcodes_v10.md @@ -461,6 +461,7 @@ Fields | 14 | CallerApplicationAddress | address | v6 | The application address of the application that called this application. ZeroAddress if this application is at the top-level. Application mode only. | | 15 | AssetCreateMinBalance | uint64 | v10 | The additional minimum balance required to create (and opt-in to) an asset. | | 16 | AssetOptInMinBalance | uint64 | v10 | The additional minimum balance required to opt-in to an asset. | +| 17 | GenesisHash | [32]byte | v10 | The Genesis Hash for the network. | ## gtxn diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 4fdcaae615..0963ee1486 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1686,17 +1686,19 @@ txn NumApprovalProgramPages txna ApprovalProgramPages 0 txn NumClearStateProgramPages txna ClearStateProgramPages 0 +pushint 1 +block BlkTimestamp +pushint 1 +block BlkSeed global AssetCreateMinBalance global AssetOptInMinBalance +global GenesisHash `, AssemblerMaxVersion) - for _, globalField := range GlobalFieldNames { - if !strings.Contains(text, globalField) { - t.Errorf("TestAssembleDisassemble missing field global %v", globalField) - } - } - for _, txnField := range TxnFieldNames { - if !strings.Contains(text, txnField) { - t.Errorf("TestAssembleDisassemble missing field txn %v", txnField) + for _, names := range [][]string{GlobalFieldNames[:], TxnFieldNames[:], blockFieldNames[:]} { + for _, f := range names { + if !strings.Contains(text, f) { + t.Errorf("TestAssembleDisassemble missing field %v", f) + } } } ops := testProg(t, text, AssemblerMaxVersion) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 0e1faec0de..883eddcec4 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -200,10 +200,12 @@ func computeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 { // "stateless" for signature purposes. type LedgerForSignature interface { BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) + GenesisHash() crypto.Digest } -// NoHeaderLedger is intended for debugging situations in which it is reasonable -// to preclude the use of `block` and `txn LastValidTime` +// NoHeaderLedger is intended for debugging TEAL in isolation(no real ledger) in +// which it is reasonable to preclude the use of `block`, `txn +// LastValidTime`. Also `global GenesisHash` is just a static value. type NoHeaderLedger struct { } @@ -212,6 +214,16 @@ func (NoHeaderLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) { return bookkeeping.BlockHeader{}, fmt.Errorf("no block header access") } +// GenesisHash returns a fixed value +func (NoHeaderLedger) GenesisHash() crypto.Digest { + return crypto.Digest{ + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + } +} + // LedgerForLogic represents ledger API for Stateful TEAL program type LedgerForLogic interface { AccountData(addr basics.Address) (ledgercore.AccountData, error) @@ -3610,8 +3622,11 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er sv.Uint = cx.Proto.MinBalance case AssetOptInMinBalance: sv.Uint = cx.Proto.MinBalance + case GenesisHash: + gh := cx.SigLedger.GenesisHash() + sv.Bytes = gh[:] default: - err = fmt.Errorf("invalid global field %d", fs.field) + return sv, fmt.Errorf("invalid global field %s", fs.field) } if fs.ftype.AVMType != sv.avmType() { @@ -5587,7 +5602,7 @@ func opBlock(cx *EvalContext) error { cx.Stack[last].Uint = uint64(hdr.TimeStamp) return nil default: - return fmt.Errorf("invalid block field %d", fs.field) + return fmt.Errorf("invalid block field %s", fs.field) } } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 06fee09c13..4682732fb5 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -575,6 +575,37 @@ func TestBadField(t *testing.T) { TestAppBytes(t, ops.Program, ep, "invalid itxn_field FirstValid") } +// TestInnerValidity logs fv and lv fields that are handled oddly (valid +// rounds are copied) so we can check if they are correct. +func TestInnerValidity(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + ep, tx, ledger := MakeSampleEnv() + tx.GenesisHash = crypto.Digest{0x01, 0x02, 0x03} + logger := TestProg(t, ` +txn FirstValid; itob; log; +txn LastValid; itob; log; +int 1`, AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: logger.Program, + }) + + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit +itxn Logs 0; btoi; txn FirstValid; ==; assert +itxn Logs 1; btoi; txn LastValid; ==; assert +itxn FirstValid; txn FirstValid; ==; assert +itxn LastValid; txn LastValid; ==; assert +int 1 +`, ep) + +} + func TestNumInnerShallow(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 436643e4c2..d25851be0d 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -3435,6 +3435,14 @@ func TestLatestTimestamp(t *testing.T) { testApp(t, source, ep) } +func TestGenHash(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + ep, _, _ := makeSampleEnv() + source := fmt.Sprintf("global GenesisHash; byte 0x%s; ==", hex.EncodeToString(testGenHash[:])) + testApp(t, source, ep) +} + func TestBlockSeed(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -3465,7 +3473,7 @@ func TestBlockSeed(t *testing.T) { testApp(t, "int 4294967310; int 1502; -; block BlkSeed; len; int 32; ==", ep, "not available") // 1501 back from lv is not - // A little silly, as it only tests the test ledger: ensure samenes and differentness + // A little silly, as it only tests the test ledger: ensure sameness and differentness testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff0; block BlkSeed; ==", ep) testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff1; block BlkSeed; !=", ep) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index bb5c2179e9..060516d65f 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -535,6 +535,9 @@ const ( // AssetOptInMinBalance is the additional minimum balance required to opt in to an asset AssetOptInMinBalance + // GenesisHash is the genesis hash for the network + GenesisHash + invalidGlobalField // compile-time constant for number of fields ) @@ -599,6 +602,7 @@ var globalFieldSpecs = [...]globalFieldSpec{ "The additional minimum balance required to create (and opt-in to) an asset."}, {AssetOptInMinBalance, StackUint64, modeAny, 10, "The additional minimum balance required to opt-in to an asset."}, + {GenesisHash, StackBytes32, modeAny, 10, "The Genesis Hash for the network."}, } func globalFieldSpecByField(f GlobalField) (globalFieldSpec, bool) { @@ -961,6 +965,7 @@ const ( BlkSeed BlockField = iota // BlkTimestamp is the Block's timestamp, seconds from epoch BlkTimestamp + invalidBlockField // compile-time constant for number of fields ) diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 7d3c2b42f2..37bfeb9bcc 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -110,12 +110,13 @@ func _() { _ = x[CallerApplicationAddress-14] _ = x[AssetCreateMinBalance-15] _ = x[AssetOptInMinBalance-16] - _ = x[invalidGlobalField-17] + _ = x[GenesisHash-17] + _ = x[invalidGlobalField-18] } -const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDOpcodeBudgetCallerApplicationIDCallerApplicationAddressAssetCreateMinBalanceAssetOptInMinBalanceinvalidGlobalField" +const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDOpcodeBudgetCallerApplicationIDCallerApplicationAddressAssetCreateMinBalanceAssetOptInMinBalanceGenesisHashinvalidGlobalField" -var _GlobalField_index = [...]uint16{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 162, 181, 205, 226, 246, 264} +var _GlobalField_index = [...]uint16{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 162, 181, 205, 226, 246, 257, 275} func (i GlobalField) String() string { if i >= GlobalField(len(_GlobalField_index)-1) { diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index f4736f6214..2fd432956a 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -112,6 +112,19 @@ func TestTxnFieldVersions(t *testing.T) { asmError = "...txna opcode was introduced in ..." txnaMode = true } + + // tack on a type check, and return a value (`int` gets compiled + // differently in different versions, so use `txn FirstValid` to get + // a positive integer) + switch fs.ftype.AVMType { + case avmUint64: // ensure the return type is uint64 by using ! + text += "; !; pop; txn FirstValid" + case avmBytes: // ensure the return type is bytes by using len + text += "; len; pop; txn FirstValid" + case avmAny: + text += "; pop; txn FirstValid" + } + // check assembler fails if version before introduction testLine(t, text, assemblerNoVersion, asmError) for v := uint64(0); v < fs.version; v++ { @@ -124,6 +137,18 @@ func TestTxnFieldVersions(t *testing.T) { ops := testProg(t, text, AssemblerMaxVersion) + // check success in AssemblerMaxVersion, fs.version + // also ensures the field returns the right type + if !fs.effects { + txgroup[0].Txn.ApprovalProgram = []byte("approve") // not in standard sample txn + txgroup[0].Txn.ClearStateProgram = []byte("clear") + ep := defaultAppParamsWithVersion(AssemblerMaxVersion, txgroup...) + testAppBytes(t, ops.Program, ep) + opsv := testProg(t, text, fs.version) + ep = defaultAppParamsWithVersion(fs.version, txgroup...) + testAppBytes(t, opsv.Program, ep) + } + preVersion := fs.version - 1 ep := defaultSigParamsWithVersion(preVersion, txgroup...) @@ -283,3 +308,36 @@ func TestAcctParamsFieldsVersions(t *testing.T) { } } + +func TestBlockFieldsVersions(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, field := range blockFieldSpecs { + text := fmt.Sprintf("txn FirstValid; int 1; - ; block %s;", field.field) + if field.ftype.AVMType == avmBytes { + text += "global ZeroAddress; concat; len" // use concat to prove we have bytes + } else { + text += "global ZeroAddress; len; +" // use + to prove we have an int + } + + testLogicRange(t, 4, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { + v := ep.Proto.LogicSigVersion + if field.version > v { + // check assembler fails if version before introduction + testProg(t, text, v, exp(1, "...was introduced in...")) + ops := testProg(t, text, field.version) // assemble in the future + ops.Program[0] = byte(v) // but set version back to before intro + if v < randomnessVersion { + testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode") + } else { + testAppBytes(t, ops.Program, ep, "invalid block field") + } + } else { + testProg(t, text, v) + testApp(t, text, ep) + } + }) + + } +} diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json index affe46601e..2141e8fe0f 100644 --- a/data/transactions/logic/langspec_v10.json +++ b/data/transactions/logic/langspec_v10.json @@ -1196,7 +1196,8 @@ "CallerApplicationID", "CallerApplicationAddress", "AssetCreateMinBalance", - "AssetOptInMinBalance" + "AssetOptInMinBalance", + "GenesisHash" ], "ArgEnumTypes": [ "uint64", @@ -1215,7 +1216,8 @@ "uint64", "address", "uint64", - "uint64" + "uint64", + "[32]byte" ], "DocCost": "1", "Doc": "global field F", diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index a111eec131..a575c493dd 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -36,6 +36,7 @@ import ( "math" "math/rand" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/committee" @@ -607,6 +608,13 @@ func (l *Ledger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Addr return basics.AppParams{}, basics.Address{}, fmt.Errorf("no app %d", appID) } +var testGenHash = crypto.Digest{0x03, 0x02, 0x03} + +// GenesisHash returns a phony genesis hash that can be tested against +func (l *Ledger) GenesisHash() crypto.Digest { + return testGenHash +} + func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) error { fbr, ok := l.balances[from] if !ok { diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index ef80fd048a..e414922e81 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -112,7 +112,7 @@ }, { "name": "variable.parameter.teal", - "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|VrfAlgorand|BlkSeed|BlkTimestamp|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2)\\b" + "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|GenesisHash|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|VrfAlgorand|BlkSeed|BlkTimestamp|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2)\\b" } ] }, diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 416c0e4c08..d2061b872a 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -78,6 +78,9 @@ func (d *DummyLedgerForSignature) BlockHdr(rnd basics.Round) (blk bookkeeping.Bl } return createDummyBlockHeader(), nil } +func (d *DummyLedgerForSignature) GenesisHash() crypto.Digest { + return crypto.Digest{} +} func (d *DummyLedgerForSignature) Latest() basics.Round { return 0 } diff --git a/ledger/eval/appcow_test.go b/ledger/eval/appcow_test.go index 2c46d02c09..a4c8d22c67 100644 --- a/ledger/eval/appcow_test.go +++ b/ledger/eval/appcow_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" @@ -99,8 +100,8 @@ func (ml *emptyLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, erro return bookkeeping.BlockHeader{}, nil } -func (ml *emptyLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) { - return bookkeeping.BlockHeader{}, nil +func (ml *emptyLedger) GenesisHash() crypto.Digest { + return crypto.Digest{} } func (ml *emptyLedger) GetStateProofNextRound() basics.Round { diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index d797a4a560..f3e6b3d049 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" @@ -65,6 +66,7 @@ type roundCowParent interface { getKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) kvGet(key string) ([]byte, bool, error) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) + GenesisHash() crypto.Digest } // When adding new fields make sure to clear them in the roundCowState.recycle() as well to avoid dirty state @@ -245,6 +247,10 @@ func (cb *roundCowState) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, erro return cb.lookupParent.BlockHdr(r) } +func (cb *roundCowState) GenesisHash() crypto.Digest { + return cb.lookupParent.GenesisHash() +} + func (cb *roundCowState) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { return cb.lookupParent.GetStateProofVerificationContext(stateProofLastAttestedRound) } diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index 225b037997..5e1253adce 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/stateproofmsg" @@ -103,8 +104,8 @@ func (ml *mockLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error return bookkeeping.BlockHeader{}, errors.New("requested blockheader not found") } -func (ml *mockLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) { - return ml.BlockHdr(rnd) +func (ml *mockLedger) GenesisHash() crypto.Digest { + panic("GenesisHash unused by tests") } func (ml *mockLedger) GetStateProofVerificationContext(rnd basics.Round) (*ledgercore.StateProofVerificationContext, error) { diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 58f7fc8a4f..714e6bddf4 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -40,6 +40,7 @@ import ( // LedgerForCowBase represents subset of Ledger functionality needed for cow business type LedgerForCowBase interface { BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) + GenesisHash() crypto.Digest CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error LookupWithoutRewards(basics.Round, basics.Address) (ledgercore.AccountData, basics.Round, error) LookupAsset(basics.Round, basics.Address, basics.AssetIndex) (ledgercore.AssetResource, error) @@ -337,6 +338,10 @@ func (x *roundCowBase) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) return x.l.BlockHdr(r) } +func (x *roundCowBase) GenesisHash() crypto.Digest { + return x.l.GenesisHash() +} + func (x *roundCowBase) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) { return x.l.GetStateProofVerificationContext(stateProofLastAttestedRound) } diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index b916558314..93a2afd2da 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -1037,6 +1037,10 @@ func (l *testCowBaseLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, err return bookkeeping.BlockHeader{}, errors.New("not implemented") } +func (l *testCowBaseLedger) GenesisHash() crypto.Digest { + panic("not implemented") +} + func (l *testCowBaseLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { return errors.New("not implemented") } From 686912b203e236a05fd397de3cbdba7d6d172e7d Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 8 Dec 2023 09:10:57 -0500 Subject: [PATCH 037/218] Simulate: Properly handle failing clear state programs (#5842) --- daemon/algod/api/algod.oas2.json | 8 + daemon/algod/api/algod.oas3.yml | 8 + .../api/server/v2/generated/data/routes.go | 56 +- .../v2/generated/experimental/routes.go | 410 +++++------ .../api/server/v2/generated/model/types.go | 6 + .../nonparticipating/private/routes.go | 416 +++++------ .../nonparticipating/public/routes.go | 210 +++--- .../generated/participating/private/routes.go | 422 ++++++------ .../generated/participating/public/routes.go | 80 +-- daemon/algod/api/server/v2/utils.go | 14 +- data/transactions/logic/debugger.go | 2 +- data/transactions/logic/eval.go | 2 +- .../logic/mocktracer/scenarios.go | 28 +- data/transactions/logic/mocktracer/tracer.go | 32 +- data/transactions/logic/tracer.go | 4 +- data/transactions/logic/tracer_test.go | 8 +- data/transactions/verify/txn_test.go | 16 +- ledger/eval/eval_test.go | 9 +- ledger/simulation/simulation_eval_test.go | 644 ++++++++++++------ ledger/simulation/simulator_test.go | 4 +- ledger/simulation/trace.go | 10 + ledger/simulation/tracer.go | 28 +- 22 files changed, 1365 insertions(+), 1052 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index b37f2001c4..e327e0e3be 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -4353,6 +4353,14 @@ "type": "string", "format": "byte" }, + "clear-state-rollback": { + "description": "If true, indicates that the clear state program failed and any persistent state changes it produced should be reverted once the program exits.", + "type": "boolean" + }, + "clear-state-rollback-error": { + "description": "The error message explaining why the clear state program failed. This field will only be populated if clear-state-rollback is true and the failure was due to an execution error.", + "type": "string" + }, "logic-sig-trace": { "description": "Program trace that contains a trace of opcode effects in a logic sig.", "type": "array", diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index c4fb9394cf..be9237b90e 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -2447,6 +2447,14 @@ }, "type": "array" }, + "clear-state-rollback": { + "description": "If true, indicates that the clear state program failed and any persistent state changes it produced should be reverted once the program exits.", + "type": "boolean" + }, + "clear-state-rollback-error": { + "description": "The error message explaining why the clear state program failed. This field will only be populated if clear-state-rollback is true and the failure was due to an execution error.", + "type": "string" + }, "inner-trace": { "description": "An array of SimulationTransactionExecTrace representing the execution trace of any inner transactions executed.", "items": { diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 1ffe766db6..4a3438b8ff 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -291,33 +291,35 @@ var swaggerSpec = []string{ "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", - "NHly8mjy8f8HAAD//3CL32ln9wAA", + "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", + "npw8mnz8/wEAAP//6aRdnSH5AAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index 3fdcd13419..e7e56520a4 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -90,210 +90,212 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index c30caa6c94..b9fc3b1baa 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -895,6 +895,12 @@ type SimulationTransactionExecTrace struct { // ClearStateProgramTrace Program trace that contains a trace of opcode effects in a clear state program. ClearStateProgramTrace *[]SimulationOpcodeTraceUnit `json:"clear-state-program-trace,omitempty"` + // ClearStateRollback If true, indicates that the clear state program failed and any persistent state changes it produced should be reverted once the program exits. + ClearStateRollback *bool `json:"clear-state-rollback,omitempty"` + + // ClearStateRollbackError The error message explaining why the clear state program failed. This field will only be populated if clear-state-rollback is true and the failure was due to an execution error. + ClearStateRollbackError *string `json:"clear-state-rollback-error,omitempty"` + // InnerTrace An array of SimulationTransactionExecTrace representing the execution trace of any inner transactions executed. InnerTrace *[]SimulationTransactionExecTrace `json:"inner-trace,omitempty"` diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 0e0d78aa42..b866cc9f42 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -139,213 +139,215 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "x0cnk4//PwAA///7V+betvkAAA==", + "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", + "nUw+/v8AAAD//31f+lNw+wAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 6d7d2c4177..713e5cd7b0 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -901,110 +901,112 @@ var swaggerSpec = []string{ "fw/0gGc71bqz1Zy2CqYw4+zTBGp75mxSiCJJh0QD2tL8mTNoO0ibMPbQR2Cu7ll3FTihqmYVjcImja4V", "+/bB6u2ascsvU6TblOw+g0YPB20ay8UMeRkeYWvGwRyPyngxbmcfNQ02FZMglEhIS4kGzQu62d1XqKck", "7Onfj796+Oj3R199TcwLJGNzUHVZ4VZfnjpijPG2neVmY8Q6y9PxTfB56RZx3lPm022qTXFnzXJbVdcM", - "7HQl2scSGrkAIscx0g/mUnuF49RB35/XdsUWefAdi6HgevbMRbbGF3DMnf4iZmQ7z2j2/NNxfmGE/8gl", - "5bf2Egvss8f250Vfhh5rg+xnQ4WRRO+D0V613OuguKiUebn2uYNA6yb9RsgDAejJ5mvkYYXdtet6ldLa", - "dtEK7B1m7UvsZe1I2xl2jpD4D3aAF6bn1e9VkdIOnE9c+PFlhZRgKe/6KKGx/F0Zf26Btecx2CKn6moN", - "yrIl0RUugnRO9azKkuyRbTvJlNhK2+g3eR5JwrTaN56pkHCMYClXNL95roE91o8RH5C96U+9CDPxQiRb", - "VKrL1QF7QQfNHWTdHW5q/hoTP/8BZo+i95wbyjkdO7cZ2k6wsfHc3wo2l5Rc4Jg2qOTh12TqarIXElKm", - "2s5M63EKogJXINnMBfDBWu/IdNu1zl+FvgIZz3zkAXkVOCUEGn9qCOsj+omZSs/JjVJ5jPo6ZBHBX4xH", - "hT0cd1wXV6zffbmyEkGBqD3LSnS7Uw5dni2dYC6dUkF3nYNv6wZuIxd1vbahNVEGlwF/+/Y3PR1SyiRe", - "stt8jrVUDlK7e6/K3ddQRcXiyI3h5o1RzK99dTVt7cieEq6t/ShZvjPMoFGQ9+N4NAcOiiksOfu7azFw", - "s3eph8BmdnePqoX1KuUoLGIia21MHkwVlNodUGXXfRapqYtZU2kpmd5ge0lvhmG/R+u9/FjVDnC1JyoP", - "iLv7tDiHqsVvXWmgVP52/VHQHO8j65jh5hYS+YR8v6bLIndGRfLtnel/wOO/PckePH74H9O/PfjqQQpP", - "vvrmwQP6zRP68JvHD+HR37568gAezr7+Zvooe/Tk0fTJoydff/VN+vjJw+mTr7/5jzuGDxmQLaC+AvTT", - "0X8nx/lcJMevT5IzA2yNE1qwn8DsDerKM4HtzwxSUzyJsKQsHz31P/2//oRNUrGsh/e/jlwbj9FC60I9", - "PTq6uLiYhJ8czTG1ONGiTBdHfh5sStWQV16fVDHJNnoCd7S2QeKmOlI4xmdvvj89I8evTyY1wYyejh5M", - "Hkweug6onBZs9HT0GH/C07PAfT9yxDZ6+uHjeHS0AJpjJQ7zxxK0ZKl/JIFmG/d/dUHnc5ATDDu3P60e", - "HXmx4uiDS7H+aGaIem1sQeagCq9vt1OU05ylvpgRU9acaCODVdhM0NpZSzUmU9tu0gcf8gwDRGzWsgpb", - "rp5kBmH285OaafmOmejVGz39LVL2xkes+0aOYchPEAz0X6c/vyJCEqfevKbpeRWt79Mz6pSUMDvDfDnx", - "9PvvEuSmpi/H+cIG88DLpWEiLux/qeZFswJkLVXFrD4dXPuZDVkEhF0VRKgZF/r4AkhqNmxY64Pkm3cf", - "vvrbx9EAQLA6hwLs6/We5vl7mwkDa4wIbMU9jPsiUsZ1gj1+UO/kGC1S1dPg8/qdZuHk91xweN+3DQ6w", - "6D7QPDcvCg6xPXiHnaeQWPDMPXrwwDMaJ8YH0B25MzUa2N/b1wq3tuZqFE8Slxioy5DsozdVDT1JC3sW", - "3ROb7+es/falieE7Tw640Galvysvtz1cZ9Hf0YxIl+eIS3n4xS7lhNtIPHOx2Avw43j01Re8Nyfc8Bya", - "E3wzaOvYvWh+4edcXHD/phF+yuWSyg2KNrrihe0+BHSu0MWGLNKe7aBME5+P3n3svfWOwpCzow+NGivZ", - "le5EG2XT6OKx45q8o/o4J44VNpQnd4+LAiPuTqvnx0Vhu8SiVxkY3n6wZkqrexPyY/g1cm9MdbQdvEqJ", - "UUO1OcXcelXTVN+KteE5DdqvRS/tRvby7f39ae/v46axo9HdPAZM4xRshakTu3LVC7Sb3BDUUtk3HLWq", - "o+tEi8S12hk4hu/dfrA+UgNKKNiZ3sVUwZ2M+hZ3PbjrE5MCeCuJqW5idTOs2ZfkrG6SxpVxjYz7Cxf6", - "XtLc0Emw3FbrC9vy/1YY/MsIg1XpvrmVzoriAOIhxsQffXC15g4hEqLuO0gYDNXq4Nsgrvlui53cm5Dj", - "9juX4xmuVt9OMc+8dyvgfQ4Cni12uEu0c3T8SYW6MKVmnwyXhjRifh/08Rcuxf2FkdUrthlIdwtsl2Cf", - "HWHMMetrY6t/SiHMIe1W/PpLi19VBd0rCWBhgOqRy/AO3FhXst61rXNMV5JYs4pywNmwCALmOtsjPK5D", - "ug2LseHCLlBYjb1miO5UqzTazRp39MauiPUjhArqd5uT57ukqy/IzjO4GWrkFojvzXXz0qjb4c3NuB2G", - "8aYnD57cHAThLrwSmvyAt/g1c8hrZWlxstqXhW3jSEdT23h/G1fiLbZUlc2yDfUDHlVVRxwHz83bNkrj", - "LmZTNtvn3JsQ3+a/rrDgsoXnwjAqnxVE5dx+ZHidQQa54/98iuPfmZAfMNdNqzEGm2ElJXyRcf304aPH", - "T9wrkl7YWK72e9Ovnzw9/vZb91ohGdcYD2D1nM7rSsunC8hz4T5wd0R3XPPg6X//838mk8mdnWxVrL/b", - "vLL9Nj8X3jqO1WGrCKBvt77wTYpp664P6k7U3Yj7/juxjt4CYn17C32yW8hg/09x+0ybZOQU0cqS2ejI", - "ccDbyB6Tfe6jsW+pb/hOdZlMyCvhmiOVOZW29gYW9lRkXlJJuQbIJp5SsayTss1g0pxhmrgkCuQKZKJY", - "VUC3lFAViCgkrDBGvi492YBgN6PHSNrPlsm/pOsgRXpaXdNauCWj2XNJ1wSr/WuiQI9tdao1+fZb8mBc", - "ay95bgZIKsTEmOuSrkc3aPWriG1oyZXnDjtC7g7QxbGHWJBq6aeqelerGn91zv3FSu6W3N3GHohz7u34", - "qR07oR3BtSDaakGwgp3GGq2qLIp8U1fnNFKeF6HiLM7MMNQ48Bn7CHaapqNKaBu9t4f41ghwJVbSJqg9", - "2QZmnaqjD6iXhzyjc24xa+6v5S4NfEdSLL3zSJAZ6HThEnZbqI+wJ+mSBvt505JxtjRQPhhfu1SDu9it", - "LRt2gM2oTZMf0mQoyKVEBx7ICBH/7Huim8dsZgtO+zYEvlIcuqZczd6q7aJVvm0jVhfP7/N6C9poI7kb", - "ymf15F2BDNFyCP/nLYL3Q3CHOX7vahLY4+UW8WeI+PeqZEJeiTpt3GpQf0rX43Xe7Ne9oFeCg/WxG8nX", - "0uKtO7USOwzjsEjx9UKs/lI3/bmsCHLk6+xslUP+bl7aIYsMub2xZs+XeIX/PVqNqHHLmLVNdhZDqEcb", - "wpzNi7bWfLP//CfUYj4JP/0MVZtPwbFuhsXgIfV8xokF/LBMB0vwWGI+qlqP93GgF+blQC577Rr3D+RG", - "WlRhaBCp/UOmkAs+V58nK9pGHXG8RKjEVpqyLSs665/8Bc/uM9dPwrf0dvWeFOMpECWWgCqDkdGxx4EN", - "lnzy4G83B6FmS9+/l4e5q5+Yu3z14PHNTX8KcsVSIGewLISkkuUb8guv+kZchdspQt2eh9bgCHNgHL1N", - "zbpgaVjE6PJMsBG69kGvWfZxNzMMCinuyQcZD/hgWESbFgVQeXkGuNt11W4yefI8jA4WVakRvys9oBgU", - "7Rkg/39GA+1OmPYuZu7yK7kF1Ff/cmzChe6K2bgKjjFSgJg9JW/5faIW1BendH8++urrHsuZmccV7ena", - "zuqBzGM7zBAD2hdtDjys1F7h9+lN7/Z+mzgesWwd7TEP66B0eLMJnhPL7ihS0I0Po+0UoSrihSgraSAc", - "dglGjFcLVtx8sUOl2TRe7dWrP1Uz1RP+XaUF24p8RvguPkWRu/FIS4AMCr3YWfsS36p3E1wVTKZc1Xtb", - "oXBM2AQmtoBf3Q0km4OyGjUlOdBZ1dZDiCHJEwGfMYTmqSLAeriQITpplH6wYAgS5c0rp3WSgb3oPPJk", - "6875pIKu/lRKaoI6KnAv2DTR8ulkSjBvjgN3dyGFFqnIbexKWRRC6up0q8kgcQ/63HYNaa+PcK8kzK1Z", - "pnba0c7wrQMY0pqUrb4YO9qZR1PMkBZb1CUr8tVzDWFpZ6IgnSauBoRPytdujW4xftayuX3pJjfdS3oH", - "tsClVKeLsjj6gP/BioQf60QprNWujvSaH2FPpaMPW0OakKXmRjaRtsx7Q4+OtoTumvXw87qk/A9Cdnr6", - "7wpZaiFt3L70bX8ojH2KsMfr0Sb/0krYVntla8Ov7oKLjNg5r1UecNDlpqLdoFGBT+21Pa4iJHzrMv68", - "FlQbcWeMZ4QG29iyNVV9aL0O8LcvdtGfwi58837yr77gc/ZKaHKyLGzDf8iuFm1I2hzO3x5br9v9BAN3", - "9XdDErt3fnjj+0DqShbZecHvofcEpSPAT0cl1nIwd/X1qDu3N/nnfZM/8yXSG2R4ey9/Ofey9OHft1fw", - "538FP/5iV3ONjuOBV7K/iS59Ddea+J4XckcYcDasluFgm18ZVe/2KtUPQvp2PLe3+BfqFLU7OTjJcoiF", - "Zpcl1k15iFD/zwr6YXaGPI9YGvoO6tj2JtMLYFgkS6QM+x2cZGpsD7EzTrhTfCv4fNaCT7DXt3LPrenh", - "CzM99Eg5TuvP8yGCxr4C0GopMvCOVTGbuaKUfdJPs1eWIU+l6bIg9suolGOdsGwJp+bNn+0UB71ia7Bb", - "YlELPIMsBangmRoQxeFGvew9hI6mfgBu3LNZ7YCHxZWrmFyaZN8ENa86lEDayFfY48wX53TIyGBFDAFO", - "DkC2Rx/sv2hOK4SKrObUE3BnY+66bbHVRu24DQDJaxRCXUd/95WYkQe26GjJMbOwbmZKeUa03BhB1ddY", - "kkBzkjYyiio4uifntPfk7FQFOqvrWVNcFxD1CT1kBEMrm/OnGz8Azyh3JN9FkBaEEg5zqtkKvMt/clsB", - "5NK3mau/sYUBjgnNMnsa602AFcgNUeVUGVmHNwPD76jmedmDYcC6AMnMFU3z2gFv1YQjW95jWxzRqX3j", - "ipdWixfZoiKyGbXob1ZXckTMyEuWSnGcz4XycahqozQsO61C3ae/9xSJ9oaEbsyq4DnjkCwFjzWw/Bmf", - "vsSHsa+xRErfx2fmYd+3rfu2CX8LrOY8Q+7kq+L3Mzn9Vwp0aa1WQiGk0W6ntqm2pf89j5I/NBuedk/S", - "hqeBU8s9DAYK2102fj7y6QiN5pfRNz80/nRlgNybalHqTFwEs6ANwIYzDqkAErTgv4TNrdXKXl2v1e06", - "vU0BHmJnq3oaaWpYP+zva/gXzXxzzpmQSDAoPRUrkKqlyN2mv/2p0t8G7/te3Ng28d3F0Up1WNnllcjA", - "jtvsoR2rPM9FBq7XcFdkqcIi4ylD/v6q32slcaS0nC80KQuiRSxdpP4woallsolVhOITBrUerbqE0y3o", - "CgjNsYMzmQJwIqZm0fVNioukCqtt+pwTF/wZFZoCuAopUlAKssRX2t8FWtXBGUPV9RY8IeAIcDULUYLM", - "qLwysOernXCewyZBZViRuz/9alTrG4fXCo3bEWtr/EXQW9URcnJhF+ph028juPbkIdlRCcSLBpgiJ5ZF", - "Di5JLoLCvXDSu39tiDq7eHW0YBYZu2aK95NcjYAqUK+Z3q8KbVkk5v7ugvjMPj1jS5TEOOXCWyBjg+VU", - "6WQXWzYvhWtRZgUBJ4xxYhy4RzV9QZV+4/KlM6ytZa8TnMfK2GaKfoCrnv2xkX+1D2Njp+Y+5KpUxI3g", - "c6Agi62Bw3rLXK9gXc2FCet+7CrJytoCd43ch6VgfIesoN0AoTrw+5vhIotDSyV1powuKhtA1IjYBsip", - "fyvAbujw7wGEqRrRlnCwfHJIOVMhcqDc5qqKojDcQiclr77rQ9OpfftY/1K/2yUuqut7OxOgwgQ4B/mF", - "xaxCU+6CKuLgIEt67nLk5q59XBdmcxgTrG2RbKN8NO6at8IjsPOQlsVc0gySDHIaMbr8Yh8T+3jbALjj", - "njyTldCQTGEmJMQ3vaZk2WtMqoYWOJ6KCY8En5DUHEGjPNcE4r7eMXIGOHaMOTk6ulMNhXNFt8iPh8u2", - "W91jwDJjmB139IAgO44+BOAePFRDXx4V+HFSmw/aU/wTlJugkiP2n2QDqm8J9fh7LaBt+AsvsMZN0WLv", - "LQ4cZZu9bGwHH+k7sjFT4xfpFmhHOV1jkl3T1BoogJPLKLdHF5TpZCakFaQTOtMgd4bO/4My7zj36bvC", - "VV0hOIK7N904yOTDJj6Oi1gQiLsuDIlMyNkCJJg7jJKHZMl4qe0TUeqxrTkqgaYLI7SHNlg7ErZhdI0J", - "JcypzHJs0Ter7k0h8TJiunXBI9CRfMSmxm/W/YOQgyoZN+t1UaZJyTXLg24Old7++Vkvby0StxaJW4vE", - "rUXi1iJxa5G4tUjcWiRuLRK3Folbi8StReKva5H4VGWSEi9x+IqNXPCkHUx5G0v5pyrlW11V3kCC1okL", - "yrTrTeyrFPTbLfYwBGmgOeKA5dAf3W2DTs++P35BlChlCiQ1EDJOipwa1QDWuuqU2ezB7LvD23a7tr0z", - "VfD4ETn9+7GvOLpwlTGb7949tvFqROlNDvdcLxrgmZVEfVMa4AbpricN9VeC76jp+ouyHCPjFfke334O", - "K8hFAdIWMyRalpGW9GdA82cONzsMPv8wk7tQ2/dmtPfjhtHLoW1JCy/m+7VSRajNuCTPgxzM9zOaK3jf", - "l4Zpx1vSItbUsrr4rCkImcl3Itu0TojZtSPcwObZqOuOMk7lJlIlqpsC0SYNLQy7coTVtWV9PHh13C7R", - "dslsF4XFpHUJKnqOt1F5tCxstWGdoWyi7qxFJ6NYjmm7FuqoAnBQYUBMk7B7Qt7Y7z5tGUCEyB2xmpl/", - "NlGMzTcrpoHvGiXCsZ4vNZfAIz56evHsjw1hZ2UKhGlFfIHd3dfLeLROzEhz4IljQMlUZJukwb5GjVso", - "Y4oqBcvp7pso5J+ujbu7fMyT7ffUp7lGngeL28aTQ6JZJ44B93DnjYbBvLnCFo7o2HOA8etm0X1sNASB", - "OP4UMyq1eN++TK+eZnPL+G4ZX3AaWxIB464geZuJTK6R8cmNLHk/z/t+DWlpgAtP8l20zqNLDta64WTN", - "YFrO59iOvuOjM0sDHI8J/olYoV3uUC64HwXZwasWxVdNUm8P1+UuQd74XV+Z8R5uB+UbdGYsC8o33uUL", - "iWLLMrc4tJ08D8tobc3wWInp2vbXZ9V+7U1+ge3WXbXN3y1ayAVVxO4vZKTkmct46tS2XvPhdU7s0Gdr", - "XrPprTVN7Hojq3PzDrki/C43U80VKUAmes3tgWocJtfBwJ7cyW0b7r/GtWET1aGHwXar8dcM4UC3hwz4", - "Gl4fQc+lOjGv0YmJNtMJG8/QotGf4hI2Z7JvHjSwpDN8M76kNrc4/ynkBaEkzRl6VwVXWpapfssp+m+C", - "hU26sSfeUN3P+575V+IuxIiHzw31llMMMqq8OlEeOIOIC+MHAM9iVTmfgzJ8NCSgGcBb7t5inJTcaGFi", - "RpYslSKxqbXmfBnZZWLfXNINmWFFE0H+ACnI1Nz6wa5bW7LSLM9dsIuZhojZW041yYEqTV4yw4HNcL6c", - "QhVyBvpCyPMKC/FePXPgoJhK4oaZH+1TbIfjlu8NgGjMtI/rNhY32wfHw86yXshPnmOMGlZjzpnSdXxE", - "B/Yb840vGU+iRHa2AOLCxdq0Re5iDThHQPeajiO9gLfc3H5aEOT4VF+OHNoeoM5ZtKejRTWNjWg5ivxa", - "B6l/B+EyJMJkbt0uf6IU0oAOvGcTN97W12/t/Z4ulsaVCzwzT3suZPvUtU/seckpEA0jWavAjXvjrAHy", - "Vv/Fl19W8vC6pEfjwbTJ7oBddtVskId48xs+JjQXfG7rKhrtUuA+MV6UGgPAr9OAByuaJ2IFUrIM1MCV", - "MsG/X9H85+qzj+MRrCFNtKQpJNaiMBRrZ+YbS6fYaJAzzWieoFY9FCA4sV+d2o923MdBt9HlEjJGNeQb", - "UkhIIbOFyJgitT4/sQUaSLqgfI5XtxTlfGFfs+NcgISqMaNRodtDxAvBrHlii9J1YTwm1hYa1u0Fmi4i", - "jWPwgjM6uyeorNGTauAeNEqO9inp41GvoG2QuqpD5yxymmxmgBTRkAcC/NQTH6JG6y3R3xL9l070sZKK", - "iLpZy1ph8RVuyzWbta67gOgNWsk+SXXh2xL9f/YS/Z4DKUKJpA0dJN4bjirCNLnAskhTIOb+KtE67xru", - "OX0dM+2Co+4qbSrXni9dUMZdTZ0qrwHh0K5bvPbtaa/FsGmZGVo0DTogLSXTG9RaaMF+Pwfz/3dG7Fcg", - "V16hKWU+ejpaaF08PTrKRUrzhVD6aPRxHD5TrYfvKvg/eF2kkGxl9KuPCLaQbM64uXMv6HwOsjYhjh5N", - "How+/t8AAAD//0RubYkspwEA", + "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", + "bwAAAP//bsB3VeaoAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index f352c24c7e..c0e0c71fe2 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -203,216 +203,218 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 107ad4eba1..0938833520 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -354,45 +354,47 @@ var swaggerSpec = []string{ "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", - "SIoQAQA=", + "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", + "/G1EEgEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index bc7e71527c..b82212a19d 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -450,12 +450,14 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra return nil } return &model.SimulationTransactionExecTrace{ - ApprovalProgramTrace: sliceOrNil(convertSlice(txnTrace.ApprovalProgramTrace, convertOpcodeTraceUnit)), - ApprovalProgramHash: digestOrNil(txnTrace.ApprovalProgramHash), - ClearStateProgramTrace: sliceOrNil(convertSlice(txnTrace.ClearStateProgramTrace, convertOpcodeTraceUnit)), - ClearStateProgramHash: digestOrNil(txnTrace.ClearStateProgramHash), - LogicSigTrace: sliceOrNil(convertSlice(txnTrace.LogicSigTrace, convertOpcodeTraceUnit)), - LogicSigHash: digestOrNil(txnTrace.LogicSigHash), + ApprovalProgramTrace: sliceOrNil(convertSlice(txnTrace.ApprovalProgramTrace, convertOpcodeTraceUnit)), + ApprovalProgramHash: digestOrNil(txnTrace.ApprovalProgramHash), + ClearStateProgramTrace: sliceOrNil(convertSlice(txnTrace.ClearStateProgramTrace, convertOpcodeTraceUnit)), + ClearStateProgramHash: digestOrNil(txnTrace.ClearStateProgramHash), + ClearStateRollback: omitEmpty(txnTrace.ClearStateRollback), + ClearStateRollbackError: omitEmpty(txnTrace.ClearStateRollbackError), + LogicSigTrace: sliceOrNil(convertSlice(txnTrace.LogicSigTrace, convertOpcodeTraceUnit)), + LogicSigHash: digestOrNil(txnTrace.LogicSigHash), InnerTrace: sliceOrNil(convertSlice(txnTrace.InnerTraces, func(trace simulation.TransactionTrace) model.SimulationTransactionExecTrace { return *convertTxnTrace(&trace) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index eda022e721..e11b41b8a2 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -98,7 +98,7 @@ func (a *debuggerEvalTracerAdaptor) BeforeOpcode(cx *EvalContext) { } // AfterProgram invokes the debugger's Complete hook -func (a *debuggerEvalTracerAdaptor) AfterProgram(cx *EvalContext, evalError error) { +func (a *debuggerEvalTracerAdaptor) AfterProgram(cx *EvalContext, pass bool, evalError error) { if a.txnDepth > 0 { // only report updates for top-level transactions, for backwards compatibility return diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 883eddcec4..836b8cb852 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1181,7 +1181,7 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) { } // Ensure we update the tracer before exiting - cx.Tracer.AfterProgram(cx, tracerErr) + cx.Tracer.AfterProgram(cx, pass, tracerErr) if x != nil { // Panic again to trigger higher-level recovery and error reporting diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index 37f8ba790f..e67907ea4d 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -379,7 +379,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, false), { - AfterProgram(logic.ModeApp, false), + AfterProgram(logic.ModeApp, ProgramResultPass), AfterTxn(protocol.ApplicationCallTx, expectedAD.EvalDelta.InnerTxns[0].ApplyData, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), @@ -397,7 +397,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, false), { - AfterProgram(logic.ModeApp, false), + AfterProgram(logic.ModeApp, ProgramResultPass), AfterTxn(protocol.ApplicationCallTx, expectedAD, false), }, }), @@ -420,6 +420,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { failureInnerProgramBytes := []byte{0x06, 0x80, 0x01, 0x78, 0xb0, 0x81, 0x00} // #pragma version 6; pushbytes "x"; log; pushint 0 failureMessage := "transaction rejected by ApprovalProgram" outcome := RejectionOutcome + programFailingResult := ProgramResultReject if shouldError { // We could use just the err opcode here, but we want to use two opcodes to maintain // trace event consistency with rejections. @@ -428,6 +429,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { failureInnerProgramBytes = []byte{0x06, 0x80, 0x01, 0x78, 0xb0, 0x00} // #pragma version 6; pushbytes "x"; log; err failureMessage = "err opcode executed" outcome = ErrorOutcome + programFailingResult = ProgramResultError } failureInnerProgram := "0x" + hex.EncodeToString(failureInnerProgramBytes) @@ -458,7 +460,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(4, shouldError), { - AfterProgram(logic.ModeApp, shouldError), + AfterProgram(logic.ModeApp, programFailingResult), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), @@ -510,11 +512,11 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, shouldError), { - AfterProgram(logic.ModeApp, shouldError), + AfterProgram(logic.ModeApp, programFailingResult), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallADNoEvalDelta, true), AfterTxnGroup(1, nil, true), // end first itxn group AfterOpcode(true), - AfterProgram(logic.ModeApp, true), + AfterProgram(logic.ModeApp, ProgramResultError), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), @@ -565,14 +567,14 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, false), { - AfterProgram(logic.ModeApp, false), + AfterProgram(logic.ModeApp, ProgramResultPass), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), }, OpcodeEvents(4, shouldError), { - AfterProgram(logic.ModeApp, shouldError), + AfterProgram(logic.ModeApp, programFailingResult), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), @@ -625,7 +627,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, false), { - AfterProgram(logic.ModeApp, false), + AfterProgram(logic.ModeApp, ProgramResultPass), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), @@ -638,7 +640,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { AfterTxn(protocol.PaymentTx, expectedInnerPay1AD, true), AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), - AfterProgram(logic.ModeApp, true), + AfterProgram(logic.ModeApp, ProgramResultError), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), @@ -691,7 +693,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, false), { - AfterProgram(logic.ModeApp, false), + AfterProgram(logic.ModeApp, ProgramResultPass), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), @@ -706,7 +708,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { AfterTxn(protocol.PaymentTx, expectedInnerPay2AD, true), AfterTxnGroup(2, nil, true), // end second itxn group AfterOpcode(true), - AfterProgram(logic.ModeApp, true), + AfterProgram(logic.ModeApp, ProgramResultError), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), @@ -754,7 +756,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, false), { - AfterProgram(logic.ModeApp, false), + AfterProgram(logic.ModeApp, ProgramResultPass), AfterTxn(protocol.ApplicationCallTx, expectedInnerAppCallAD, false), AfterTxnGroup(1, nil, false), // end first itxn group AfterOpcode(false), @@ -772,7 +774,7 @@ func GetTestScenarios() map[string]TestScenarioGenerator { }, OpcodeEvents(3, shouldError), { - AfterProgram(logic.ModeApp, shouldError), + AfterProgram(logic.ModeApp, programFailingResult), AfterTxn(protocol.ApplicationCallTx, expectedADNoED, true), }, }), diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 13a6d92d7a..f40faf1be1 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -74,6 +74,9 @@ type Event struct { // only for BeforeTxnGroup and AfterTxnGroup GroupSize int + // only for AfterProgram + Pass bool + // only for AfterOpcode, AfterProgram, AfterTxn, and AfterTxnGroup HasError bool @@ -111,9 +114,21 @@ func AfterTxn(txnType protocol.TxType, ad transactions.ApplyData, hasError bool) return Event{Type: AfterTxnEvent, TxnType: txnType, TxnApplyData: ad, HasError: hasError} } +// ProgramResult represents the result of a program execution +type ProgramResult int + +const ( + // ProgramResultPass represents a program that passed + ProgramResultPass ProgramResult = iota + // ProgramResultReject represents a program that rejected + ProgramResultReject + // ProgramResultError represents a program that errored + ProgramResultError +) + // AfterProgram creates a new Event with the type AfterProgramEvent -func AfterProgram(mode logic.RunMode, hasError bool) Event { - return Event{Type: AfterProgramEvent, LogicEvalMode: mode, HasError: hasError} +func AfterProgram(mode logic.RunMode, result ProgramResult) Event { + return Event{Type: AfterProgramEvent, LogicEvalMode: mode, Pass: result == ProgramResultPass, HasError: result == ProgramResultError} } // BeforeOpcode creates a new Event with the type BeforeOpcodeEvent @@ -189,8 +204,17 @@ func (d *Tracer) BeforeProgram(cx *logic.EvalContext) { } // AfterProgram mocks the logic.EvalTracer.AfterProgram method -func (d *Tracer) AfterProgram(cx *logic.EvalContext, evalError error) { - d.Events = append(d.Events, AfterProgram(cx.RunMode(), evalError != nil)) +func (d *Tracer) AfterProgram(cx *logic.EvalContext, pass bool, evalError error) { + var result ProgramResult + if pass { + result = ProgramResultPass + } else if evalError != nil { + result = ProgramResultError + } else { + result = ProgramResultReject + + } + d.Events = append(d.Events, AfterProgram(cx.RunMode(), result)) } // BeforeOpcode mocks the logic.EvalTracer.BeforeOpcode method diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index 5894409ba9..37603c7944 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -151,7 +151,7 @@ type EvalTracer interface { BeforeProgram(cx *EvalContext) // AfterProgram is called after an app or LogicSig program is evaluated. - AfterProgram(cx *EvalContext, evalError error) + AfterProgram(cx *EvalContext, pass bool, evalError error) // BeforeOpcode is called before the op is evaluated BeforeOpcode(cx *EvalContext) @@ -188,7 +188,7 @@ func (n NullEvalTracer) AfterTxn(ep *EvalParams, groupIndex int, ad transactions func (n NullEvalTracer) BeforeProgram(cx *EvalContext) {} // AfterProgram does nothing -func (n NullEvalTracer) AfterProgram(cx *EvalContext, evalError error) {} +func (n NullEvalTracer) AfterProgram(cx *EvalContext, pass bool, evalError error) {} // BeforeOpcode does nothing func (n NullEvalTracer) BeforeOpcode(cx *EvalContext) {} diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go index 5d44cbe2f1..323447464b 100644 --- a/data/transactions/logic/tracer_test.go +++ b/data/transactions/logic/tracer_test.go @@ -44,7 +44,7 @@ func getSimpleTracerTestCases(mode RunMode) []tracerTestCase { }, mocktracer.OpcodeEvents(35, false), { - mocktracer.AfterProgram(mode, false), + mocktracer.AfterProgram(mode, mocktracer.ProgramResultPass), }, }), }, @@ -58,7 +58,7 @@ func getSimpleTracerTestCases(mode RunMode) []tracerTestCase { }, mocktracer.OpcodeEvents(36, false), { - mocktracer.AfterProgram(mode, false), + mocktracer.AfterProgram(mode, mocktracer.ProgramResultReject), }, }), }, @@ -72,7 +72,7 @@ func getSimpleTracerTestCases(mode RunMode) []tracerTestCase { }, mocktracer.OpcodeEvents(36, true), { - mocktracer.AfterProgram(mode, true), + mocktracer.AfterProgram(mode, mocktracer.ProgramResultError), }, }), }, @@ -90,7 +90,7 @@ func getPanicTracerTestCase(mode RunMode) tracerTestCase { }, mocktracer.OpcodeEvents(36, true), { - mocktracer.AfterProgram(mode, true), + mocktracer.AfterProgram(mode, mocktracer.ProgramResultError), }, }), } diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index d2061b872a..1e7793248b 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -386,13 +386,13 @@ pushint 1`, { mocktracer.BeforeProgram(logic.ModeSig), // first txn start mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op - mocktracer.AfterProgram(logic.ModeSig, false), // first txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // first txn end // nothing for second txn (not signed with a LogicSig) mocktracer.BeforeProgram(logic.ModeSig), // third txn start }, mocktracer.OpcodeEvents(3, false), // third txn LogicSig: 3 ops { - mocktracer.AfterProgram(logic.ModeSig, false), // third txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // third txn end }, }), }, @@ -409,13 +409,13 @@ pushint 0`, { mocktracer.BeforeProgram(logic.ModeSig), // first txn start mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op - mocktracer.AfterProgram(logic.ModeSig, false), // first txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // first txn end // nothing for second txn (not signed with a LogicSig) mocktracer.BeforeProgram(logic.ModeSig), // third txn start }, mocktracer.OpcodeEvents(3, false), // third txn LogicSig: 3 ops { - mocktracer.AfterProgram(logic.ModeSig, false), // third txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultReject), // third txn end }, }), }, @@ -434,13 +434,13 @@ pop`, { mocktracer.BeforeProgram(logic.ModeSig), // first txn start mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op - mocktracer.AfterProgram(logic.ModeSig, false), // first txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // first txn end // nothing for second txn (not signed with a LogicSig) mocktracer.BeforeProgram(logic.ModeSig), // third txn start }, mocktracer.OpcodeEvents(3, true), // third txn LogicSig: 3 ops { - mocktracer.AfterProgram(logic.ModeSig, true), // third txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultError), // third txn end }, }), }, @@ -456,7 +456,7 @@ pushint 1`, expectedEvents: []mocktracer.Event{ mocktracer.BeforeProgram(logic.ModeSig), // first txn start mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), // first txn LogicSig: 1 op - mocktracer.AfterProgram(logic.ModeSig, false), // first txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultReject), // first txn end // execution stops at rejection }, }, @@ -472,7 +472,7 @@ pushint 1`, expectedEvents: []mocktracer.Event{ mocktracer.BeforeProgram(logic.ModeSig), // first txn start mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(true), // first txn LogicSig: 1 op - mocktracer.AfterProgram(logic.ModeSig, true), // first txn end + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultError), // first txn end // execution stops at error }, }, diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 93a2afd2da..cb6bf08784 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -543,7 +543,7 @@ int 1`, }, mocktracer.OpcodeEvents(3, false), { - mocktracer.AfterProgram(logic.ModeApp, false), + mocktracer.AfterProgram(logic.ModeApp, mocktracer.ProgramResultPass), mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, false), // end basicAppCallTxn mocktracer.BeforeTxn(protocol.PaymentTx), // start payTxn mocktracer.AfterTxn(protocol.PaymentTx, expectedPayTxnAD, false), // end payTxn @@ -559,6 +559,11 @@ int 1`, delete(expectedBasicAppCallDelta.Txids, txgroup[0].Txn.ID()) hasError := testCase.firstTxnBehavior == "error" + expectedProgramResult := mocktracer.ProgramResultReject + if hasError { + expectedProgramResult = mocktracer.ProgramResultError + } + // EvalDeltas are removed from failed app call transactions expectedBasicAppCallAD.EvalDelta = transactions.EvalDelta{} expectedEvents = append(expectedEvents, mocktracer.FlattenEvents([][]mocktracer.Event{ @@ -569,7 +574,7 @@ int 1`, }, mocktracer.OpcodeEvents(3, hasError), { - mocktracer.AfterProgram(logic.ModeApp, hasError), + mocktracer.AfterProgram(logic.ModeApp, expectedProgramResult), mocktracer.AfterTxn(protocol.ApplicationCallTx, expectedBasicAppCallAD, true), // end basicAppCallTxn mocktracer.AfterTxnGroup(3, &expectedBasicAppCallDelta, true), }, diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 3fbe61512c..7d2bf83809 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -865,6 +865,254 @@ int 0 }) } +const returnFirstAppArgProgram = `#pragma version 6 +byte "counter" +dup +app_global_get +int 1 ++ +app_global_put + +txn ApplicationID +bz end + +txn OnCompletion +int OptIn +== +bnz end + +txn ApplicationArgs 0 +btoi +return + +end: +int 1 +return` + +func TestClearStateRejection(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + user := env.Accounts[1] + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: returnFirstAppArgProgram, + ClearStateProgram: returnFirstAppArgProgram, + GlobalStateSchema: basics.StateSchema{ + NumUint: 1, + }, + }) + env.OptIntoApp(user.Addr, appID) + + clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: user.Addr, + ApplicationID: appID, + OnCompletion: transactions.ClearStateOC, + ApplicationArgs: [][]byte{{0}}, + }) + otherAppCall := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: user.Addr, + ApplicationID: appID, + ApplicationArgs: [][]byte{{1}}, + }) + + txntest.Group(&clearStateTxn, &otherAppCall) + + signedClearStateTxn := clearStateTxn.Txn().Sign(user.Sk) + signedOtherAppCall := otherAppCall.Txn().Sign(user.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn, signedOtherAppCall}}, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + // No EvalDelta changes because the clear state failed + AppBudgetConsumed: 16, + }, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + GlobalDelta: basics.StateDelta{ + "counter": { + Action: basics.SetUintAction, + Uint: 3, + }, + }, + }, + }, + }, + AppBudgetConsumed: 16, + }, + }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 32, + }, + }, + }, + } + }) +} + +func TestClearStateError(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + user := env.Accounts[1] + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: returnFirstAppArgProgram, + ClearStateProgram: returnFirstAppArgProgram, + GlobalStateSchema: basics.StateSchema{ + NumUint: 1, + }, + }) + env.OptIntoApp(user.Addr, appID) + + clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: user.Addr, + ApplicationID: appID, + OnCompletion: transactions.ClearStateOC, + ApplicationArgs: [][]byte{}, // No app args, will cause error + }) + otherAppCall := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: user.Addr, + ApplicationID: appID, + ApplicationArgs: [][]byte{{1}}, + }) + + txntest.Group(&clearStateTxn, &otherAppCall) + + signedClearStateTxn := clearStateTxn.Txn().Sign(user.Sk) + signedOtherAppCall := otherAppCall.Txn().Sign(user.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn, signedOtherAppCall}}, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + // No EvalDelta changes because the clear state failed + AppBudgetConsumed: 14, + }, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + GlobalDelta: basics.StateDelta{ + "counter": { + Action: basics.SetUintAction, + Uint: 3, + }, + }, + }, + }, + }, + AppBudgetConsumed: 16, + }, + }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 30, + }, + }, + }, + } + }) +} + +func TestErrorAfterClearStateError(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + user := env.Accounts[1] + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: returnFirstAppArgProgram, + ClearStateProgram: returnFirstAppArgProgram, + GlobalStateSchema: basics.StateSchema{ + NumUint: 1, + }, + }) + env.OptIntoApp(user.Addr, appID) + + clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: user.Addr, + ApplicationID: appID, + OnCompletion: transactions.ClearStateOC, + ApplicationArgs: [][]byte{}, // No app args, will cause error + }) + otherAppCall := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: user.Addr, + ApplicationID: appID, + ApplicationArgs: [][]byte{{0}}, + }) + + txntest.Group(&clearStateTxn, &otherAppCall) + + signedClearStateTxn := clearStateTxn.Txn().Sign(user.Sk) + signedOtherAppCall := otherAppCall.Txn().Sign(user.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn, signedOtherAppCall}}, + }, + expectedError: "transaction rejected by ApprovalProgram", + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + // No EvalDelta changes because the clear state failed + AppBudgetConsumed: 14, + }, + { + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + GlobalDelta: basics.StateDelta{ + "counter": { + Action: basics.SetUintAction, + Uint: 3, + }, + }, + }, + }, + }, + AppBudgetConsumed: 16, + }, + }, + AppBudgetAdded: 1400, + AppBudgetConsumed: 30, + FailedAt: simulation.TxnPath{1}, + }, + }, + }, + } + }) +} + func TestAppCallOverBudget(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -3992,242 +4240,216 @@ int 1`, }) } -func TestGlobalStateTypeChange(t *testing.T) { +func TestAppLocalGlobalStateChangeClearStateRollback(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + for _, shouldError := range []bool{false, true} { + shouldError := shouldError + t.Run(fmt.Sprintf("shouldError=%v", shouldError), func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] - simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { - sender := env.Accounts[0] - - futureAppID := basics.AppIndex(1001) + approvalProgram := `#pragma version 8 +int 1` + clearStateProgram := `#pragma version 8 +byte "global key" +byte "global value" +app_global_put - createTxn := env.TxnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: 0, - GlobalStateSchema: basics.StateSchema{NumUint: 1, NumByteSlice: 1}, - ApprovalProgram: `#pragma version 8 -txn ApplicationID -bz end // Do nothing during create +txn Sender +byte "local key" +byte "local value" +app_local_put +` -byte "global-key" -int 0xdecaf -app_global_put -byte "global-key" -byte "welt am draht" -app_global_put + if shouldError { + clearStateProgram += "err" + } else { + clearStateProgram += "int 0" + } -end: - int 1 -`, - ClearStateProgram: `#pragma version 8 -int 1`, - }) + createdAppID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + GlobalStateSchema: basics.StateSchema{NumByteSlice: 1}, + LocalStateSchema: basics.StateSchema{NumByteSlice: 1}, + ApprovalProgram: approvalProgram, + ClearStateProgram: clearStateProgram, + }) - op, err := logic.AssembleString(createTxn.ApprovalProgram.(string)) - require.NoError(t, err) - progHash := crypto.Hash(op.Program) + op, err := logic.AssembleString(clearStateProgram) + require.NoError(t, err) + progHash := crypto.Hash(op.Program) - globalStateCall := env.TxnInfo.NewTxn(txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: sender.Addr, - ApplicationID: futureAppID, - }) + env.OptIntoApp(sender.Addr, createdAppID) - txntest.Group(&createTxn, &globalStateCall) + clearStateTxn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: createdAppID, + OnCompletion: transactions.ClearStateOC, + }) - signedCreate := createTxn.Txn().Sign(sender.Sk) - signedGlobalStateCall := globalStateCall.Txn().Sign(sender.Sk) + signedClearStateTxn := clearStateTxn.Txn().Sign(sender.Sk) - return simulationTestCase{ - input: simulation.Request{ - TxnGroups: [][]transactions.SignedTxn{ - {signedCreate, signedGlobalStateCall}, - }, - TraceConfig: simulation.ExecTraceConfig{ - Enable: true, - Stack: true, - Scratch: true, - State: true, - }, - }, - developerAPI: true, - expected: simulation.Result{ - Version: simulation.ResultLatestVersion, - LastRound: env.TxnInfo.LatestRound(), - TraceConfig: simulation.ExecTraceConfig{ - Enable: true, - Stack: true, - Scratch: true, - State: true, - }, - TxnGroups: []simulation.TxnGroupResult{ + clearStateRollbackError := "" + clearStateProgramTrace := []simulation.OpcodeTraceUnit{ { - Txns: []simulation.TxnResult{ - // App creation + PC: 1, + StackAdded: []basics.TealValue{ { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - ApplicationID: futureAppID, - }, + Type: basics.TealBytesType, + Bytes: "global key", + }, + }, + }, + { + PC: 13, + StackAdded: []basics.TealValue{ + { + Type: basics.TealBytesType, + Bytes: "global value", + }, + }, + }, + { + PC: 27, + StackPopCount: 2, + StateChanges: []simulation.StateOperation{ + { + AppStateOp: logic.AppStateWrite, + AppState: logic.GlobalState, + AppID: createdAppID, + Key: "global key", + NewValue: basics.TealValue{ + Type: basics.TealBytesType, + Bytes: "global value", }, - AppBudgetConsumed: 4, - Trace: &simulation.TransactionTrace{ - ApprovalProgramTrace: []simulation.OpcodeTraceUnit{ - { - PC: 1, - }, - { - PC: 14, - StackAdded: []basics.TealValue{ - { - Type: basics.TealUintType, - }, - }, - }, - { - PC: 16, - StackPopCount: 1, - }, - { - PC: 42, - StackAdded: []basics.TealValue{ - { - Type: basics.TealUintType, - Uint: 1, - }, - }, - }, - }, - ApprovalProgramHash: progHash, + }, + }, + }, + { + PC: 28, + StackAdded: []basics.TealValue{ + { + Type: basics.TealBytesType, + Bytes: string(sender.Addr[:]), + }, + }, + }, + { + PC: 30, + StackAdded: []basics.TealValue{ + { + Type: basics.TealBytesType, + Bytes: "local key", + }, + }, + }, + { + PC: 41, + StackAdded: []basics.TealValue{ + { + Type: basics.TealBytesType, + Bytes: "local value", + }, + }, + }, + { + PC: 54, + StackPopCount: 3, + StateChanges: []simulation.StateOperation{ + { + AppStateOp: logic.AppStateWrite, + AppState: logic.LocalState, + AppID: createdAppID, + Account: sender.Addr, + Key: "local key", + NewValue: basics.TealValue{ + Type: basics.TealBytesType, + Bytes: "local value", }, }, - // Global + }, + }, + { + PC: 55, + StackAdded: []basics.TealValue{ { - Txn: transactions.SignedTxnWithAD{ - ApplyData: transactions.ApplyData{ - EvalDelta: transactions.EvalDelta{ - GlobalDelta: basics.StateDelta{ - "global-key": basics.ValueDelta{ - Bytes: "welt am draht", - Action: basics.SetBytesAction, - }, - }, + Type: basics.TealUintType, + Uint: 0, + }, + }, + }, + } + + if shouldError { + clearStateRollbackError = "err opcode executed" + clearStateProgramTrace[len(clearStateProgramTrace)-1].StackAdded = nil + } + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{{signedClearStateTxn}}, + TraceConfig: simulation.ExecTraceConfig{ + Enable: true, + Stack: true, + Scratch: true, + State: true, + }, + }, + developerAPI: true, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + TraceConfig: simulation.ExecTraceConfig{ + Enable: true, + Stack: true, + Scratch: true, + State: true, + }, + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { + AppBudgetConsumed: 8, + Trace: &simulation.TransactionTrace{ + ClearStateProgramTrace: clearStateProgramTrace, + ClearStateProgramHash: progHash, + ClearStateRollback: true, + ClearStateRollbackError: clearStateRollbackError, }, }, }, - AppBudgetConsumed: 10, - Trace: &simulation.TransactionTrace{ - ApprovalProgramTrace: []simulation.OpcodeTraceUnit{ - { - PC: 1, - }, - { - PC: 14, - StackAdded: []basics.TealValue{ - { - Type: basics.TealUintType, - Uint: uint64(futureAppID), - }, - }, - }, - { - PC: 16, - StackPopCount: 1, - }, - { - PC: 19, - StackAdded: []basics.TealValue{ - { - Type: basics.TealBytesType, - Bytes: "global-key", - }, - }, - }, - { - PC: 20, - StackAdded: []basics.TealValue{ - { - Type: basics.TealUintType, - Uint: 0xdecaf, - }, - }, - }, - { - PC: 24, - StateChanges: []simulation.StateOperation{ - { - AppStateOp: logic.AppStateWrite, - AppState: logic.GlobalState, - AppID: futureAppID, - Key: "global-key", - NewValue: basics.TealValue{ - Type: basics.TealUintType, - Uint: 0xdecaf, - }, - }, - }, - StackPopCount: 2, - }, - { - PC: 25, - StackAdded: []basics.TealValue{ - { - Type: basics.TealBytesType, - Bytes: "global-key", - }, - }, - }, - { - PC: 26, - StackAdded: []basics.TealValue{ - { - Type: basics.TealBytesType, - Bytes: "welt am draht", - }, - }, - }, - { - PC: 41, - StackPopCount: 2, - StateChanges: []simulation.StateOperation{ - { - AppStateOp: logic.AppStateWrite, - AppState: logic.GlobalState, - AppID: futureAppID, - Key: "global-key", - NewValue: basics.TealValue{ - Type: basics.TealBytesType, - Bytes: "welt am draht", - }, - }, - }, - }, - { - PC: 42, - StackAdded: []basics.TealValue{ - { - Type: basics.TealUintType, - Uint: 1, - }, - }, - }, + AppBudgetAdded: 700, + AppBudgetConsumed: 8, + }, + }, + InitialStates: &simulation.ResourcesInitialStates{ + AllAppsInitialStates: simulation.AppsInitialStates{ + createdAppID: { + AppLocals: map[basics.Address]simulation.AppKVPairs{}, + AppGlobals: simulation.AppKVPairs{}, + AppBoxes: simulation.AppKVPairs{}, + // It's fine to leave the keys in "CreatedX" for two reasons: + // 1. These fields really just mean state was accessed that + // didn't exist before, so we shouldn't try to report an + // initial value. + // 2. These values are not included in the REST API, so they are + // not going to confuse users. + CreatedGlobals: util.MakeSet("global key"), + CreatedBoxes: make(util.Set[string]), + CreatedLocals: map[basics.Address]util.Set[string]{ + sender.Addr: util.MakeSet("local key"), }, - ApprovalProgramHash: progHash, }, }, + CreatedApp: util.Set[basics.AppIndex]{}, }, - AppBudgetAdded: 1400, - AppBudgetConsumed: 14, }, - }, - InitialStates: &simulation.ResourcesInitialStates{ - AllAppsInitialStates: make(simulation.AppsInitialStates), - CreatedApp: util.MakeSet(futureAppID), - }, - }, - } - }) + } + }) + }) + } } func TestGlobalStateTypeChangeFailure(t *testing.T) { diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index a53d2f8ec3..5c9d2ad8e4 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -222,7 +222,7 @@ int 1`, mocktracer.BeforeProgram(logic.ModeSig), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), - mocktracer.AfterProgram(logic.ModeSig, false), + mocktracer.AfterProgram(logic.ModeSig, mocktracer.ProgramResultPass), // Txn evaluation mocktracer.BeforeBlock(block.Block().Round()), mocktracer.BeforeTxnGroup(2), @@ -232,7 +232,7 @@ int 1`, mocktracer.BeforeProgram(logic.ModeApp), mocktracer.BeforeOpcode(), mocktracer.AfterOpcode(false), - mocktracer.AfterProgram(logic.ModeApp, false), + mocktracer.AfterProgram(logic.ModeApp, mocktracer.ProgramResultPass), mocktracer.AfterTxn(protocol.ApplicationCallTx, evalBlock.Payset[1].ApplyData, false), mocktracer.AfterTxnGroup(2, &expectedDelta, false), //Block evaluation diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index afc6a2124e..99ac9b416e 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -283,16 +283,26 @@ type TransactionTrace struct { ApprovalProgramTrace []OpcodeTraceUnit // ApprovalProgramHash stands for the hash digest of approval program bytecode executed during simulation ApprovalProgramHash crypto.Digest + // ClearStateProgramTrace stands for a slice of OpcodeTraceUnit over application call on clear-state program ClearStateProgramTrace []OpcodeTraceUnit // ClearStateProgramHash stands for the hash digest of clear state program bytecode executed during simulation ClearStateProgramHash crypto.Digest + // ClearStateRollback, if true, indicates that the clear state program failed and any persistent state changes + // it produced should be reverted once the program exits. + ClearStateRollback bool + // ClearStateRollbackError contains the error message explaining why the clear state program failed. This + // field will only be populated if ClearStateRollback is true and the failure was due to an execution error. + ClearStateRollbackError string + // LogicSigTrace contains the trace for a logicsig evaluation, if the transaction is approved by a logicsig. LogicSigTrace []OpcodeTraceUnit // LogicSigHash stands for the hash digest of logic sig bytecode executed during simulation LogicSigHash crypto.Digest + // programTraceRef points to one of ApprovalProgramTrace, ClearStateProgramTrace, and LogicSigTrace during simulation. programTraceRef *[]OpcodeTraceUnit + // InnerTraces contains the traces for inner transactions, if this transaction spawned any. This // object only contains traces for inners that are immediate children of this transaction. // Grandchild traces will be present inside the TransactionTrace of their parent. diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index 2bc600ee38..e590250c94 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -185,12 +185,14 @@ func (tracer *evalTracer) AfterTxnGroup(ep *logic.EvalParams, deltas *ledgercore } } -func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData) { +func (tracer *evalTracer) saveApplyData(applyData transactions.ApplyData, omitEvalDelta bool) { applyDataOfCurrentTxn := tracer.mustGetApplyDataAtPath(tracer.absolutePath()) - // Copy everything except the EvalDelta, since that has been kept up-to-date after every op evalDelta := applyDataOfCurrentTxn.EvalDelta *applyDataOfCurrentTxn = applyData - applyDataOfCurrentTxn.EvalDelta = evalDelta + if omitEvalDelta { + // If omitEvalDelta is true, restore the EvalDelta from applyDataOfCurrentTxn + applyDataOfCurrentTxn.EvalDelta = evalDelta + } } func (tracer *evalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) { @@ -245,7 +247,7 @@ func (tracer *evalTracer) BeforeTxn(ep *logic.EvalParams, groupIndex int) { func (tracer *evalTracer) AfterTxn(ep *logic.EvalParams, groupIndex int, ad transactions.ApplyData, evalError error) { tracer.handleError(evalError) - tracer.saveApplyData(ad) + tracer.saveApplyData(ad, evalError != nil) // if the current transaction + simulation condition would lead to exec trace making // we should clean them up from tracer.execTraceStack. if tracer.result.ReturnTrace() { @@ -412,7 +414,9 @@ func (tracer *evalTracer) AfterOpcode(cx *logic.EvalContext, evalError error) { } if cx.RunMode() == logic.ModeApp { - tracer.handleError(evalError) + if cx.TxnGroup[groupIndex].Txn.ApplicationCallTxnFields.OnCompletion != transactions.ClearStateOC { + tracer.handleError(evalError) + } if evalError == nil && tracer.unnamedResourcePolicy != nil { if err := tracer.unnamedResourcePolicy.tracker.reconcileBoxWriteBudget(cx.BoxDirtyBytes(), cx.Proto.BytesPerBoxReference); err != nil { // This should never happen, since we limit the IO budget to tracer.unnamedResourcePolicy.assignment.maxPossibleBoxIOBudget @@ -481,7 +485,7 @@ func (tracer *evalTracer) BeforeProgram(cx *logic.EvalContext) { } } -func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, evalError error) { +func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, pass bool, evalError error) { groupIndex := cx.GroupIndex() if cx.RunMode() == logic.ModeSig { @@ -497,5 +501,15 @@ func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, evalError error) { // If it is an inner app call, roll up its cost to the top level transaction. tracer.result.TxnGroups[0].Txns[tracer.relativeCursor[0]].AppBudgetConsumed += uint64(cx.Cost()) - tracer.handleError(evalError) + if cx.TxnGroup[groupIndex].Txn.ApplicationCallTxnFields.OnCompletion == transactions.ClearStateOC { + if tracer.result.ReturnTrace() && (!pass || evalError != nil) { + txnTrace := tracer.execTraceStack[len(tracer.execTraceStack)-1] + txnTrace.ClearStateRollback = true + if evalError != nil { + txnTrace.ClearStateRollbackError = evalError.Error() + } + } + } else { + tracer.handleError(evalError) + } } From b28b145c5fe5a5f97f5b5e327f0f8ee49f551c81 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:44:11 -0500 Subject: [PATCH 038/218] consensus: Enable dynamic round times in vfuture. (#5860) --- config/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/consensus.go b/config/consensus.go index 95004e91f9..992cc90ce9 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1398,7 +1398,7 @@ func initConsensusProtocols() { // Setting DynamicFilterTimeout in vFuture will impact e2e test performance // by reducing round time. Hence, it is commented out for now. - // vFuture.DynamicFilterTimeout = true + vFuture.DynamicFilterTimeout = true Consensus[protocol.ConsensusFuture] = vFuture From 14119045e3f0c3a5553215ae20a42044d1b01099 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Fri, 8 Dec 2023 19:30:23 +0000 Subject: [PATCH 039/218] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +0 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885 From 4b872f2324c6b27dbabd4017bf1b9f4088bdfdf6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 8 Dec 2023 17:43:23 -0500 Subject: [PATCH 040/218] Chore: Update testing to use current python SDK. (#5861) --- test/scripts/e2e.sh | 7 ++++--- test/scripts/e2e_client_runner.py | 5 +++-- test/scripts/e2e_subs/example.py | 2 +- test/scripts/e2e_subs/goal/goal.py | 2 +- test/scripts/e2e_subs/min_balance.py | 2 -- test/scripts/e2e_subs/shared-resources.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/scripts/e2e.sh b/test/scripts/e2e.sh index 760c3170ad..a3b2c756fa 100755 --- a/test/scripts/e2e.sh +++ b/test/scripts/e2e.sh @@ -125,9 +125,10 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then . "${TEMPDIR}/ve/bin/activate" "${TEMPDIR}/ve/bin/pip3" install --upgrade pip - # Pin a version of our python SDK's so that breaking changes don't spuriously break our tests. - # Please update as necessary. - "${TEMPDIR}/ve/bin/pip3" install py-algorand-sdk==1.17.0 + # Pin major version of our python SDK's so that breaking changes + # don't spuriously break our tests. If a minor version breaks our + # tests, we ought to find out. + "${TEMPDIR}/ve/bin/pip3" install 'py-algorand-sdk==2.*' # Enable remote debugging: "${TEMPDIR}/ve/bin/pip3" install --upgrade debugpy diff --git a/test/scripts/e2e_client_runner.py b/test/scripts/e2e_client_runner.py index 7b425ef925..3d5888696f 100755 --- a/test/scripts/e2e_client_runner.py +++ b/test/scripts/e2e_client_runner.py @@ -114,11 +114,12 @@ def _script_thread_inner(runset, scriptname, timeout): params = algod.suggested_params() round = params.first max_init_wait_rounds = 5 - txn = algosdk.transaction.PaymentTxn(sender=maxpubaddr, fee=params.min_fee, first=round, last=round+max_init_wait_rounds, gh=params.gh, receiver=addr, amt=1000000000000, flat_fee=True) + params.last = params.first + max_init_wait_rounds + txn = algosdk.transaction.PaymentTxn(maxpubaddr, params, addr, 1_000_000_000_000) stxn = kmd.sign_transaction(pubw, '', txn) txid = algod.send_transaction(stxn) ptxinfo = None - for i in range(max_init_wait_rounds): + for _ in range(max_init_wait_rounds): txinfo = algod.pending_transaction_info(txid) if txinfo.get('round'): break diff --git a/test/scripts/e2e_subs/example.py b/test/scripts/e2e_subs/example.py index 4852ac9d76..9e4e97097a 100755 --- a/test/scripts/e2e_subs/example.py +++ b/test/scripts/e2e_subs/example.py @@ -4,7 +4,7 @@ import sys from goal import Goal -import algosdk.future.transaction as txn +import algosdk.transaction as txn from datetime import datetime stamp = datetime.now().strftime("%Y%m%d_%H%M%S") diff --git a/test/scripts/e2e_subs/goal/goal.py b/test/scripts/e2e_subs/goal/goal.py index d36241fd78..812afe8976 100755 --- a/test/scripts/e2e_subs/goal/goal.py +++ b/test/scripts/e2e_subs/goal/goal.py @@ -6,7 +6,7 @@ import subprocess import algosdk -import algosdk.future.transaction as txn +import algosdk.transaction as txn import algosdk.encoding as enc diff --git a/test/scripts/e2e_subs/min_balance.py b/test/scripts/e2e_subs/min_balance.py index b7376fd37f..dfeacbe3b6 100755 --- a/test/scripts/e2e_subs/min_balance.py +++ b/test/scripts/e2e_subs/min_balance.py @@ -5,8 +5,6 @@ from pathlib import PurePath import sys -import algosdk.future.transaction as txn - from goal import Goal # Set INTERACTIVE True if you want to run a remote debugger interactively on the given PORT diff --git a/test/scripts/e2e_subs/shared-resources.py b/test/scripts/e2e_subs/shared-resources.py index b5a93f96cf..2df56f7893 100755 --- a/test/scripts/e2e_subs/shared-resources.py +++ b/test/scripts/e2e_subs/shared-resources.py @@ -3,7 +3,7 @@ import os import sys import algosdk.encoding as enc -import algosdk.future.transaction as txn +import algosdk.transaction as txn from goal import Goal from datetime import datetime From 548fbb48f877ab24a6edfb12777134ccc690fc95 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 11 Dec 2023 12:32:19 -0500 Subject: [PATCH 041/218] AVM: Require every global field to get tested (#5864) --- data/transactions/logic/eval_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index fd8b9ff806..481f9544fe 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1232,6 +1232,7 @@ const globalV9TestProgram = globalV8TestProgram + ` const globalV10TestProgram = globalV9TestProgram + ` global AssetCreateMinBalance; int 1001; ==; && global AssetOptInMinBalance; int 1001; ==; && +global GenesisHash; len; int 32; ==; && ` func TestGlobal(t *testing.T) { @@ -1254,12 +1255,16 @@ func TestGlobal(t *testing.T) { 7: {CallerApplicationAddress, globalV7TestProgram}, 8: {CallerApplicationAddress, globalV8TestProgram}, 9: {CallerApplicationAddress, globalV9TestProgram}, - 10: {AssetOptInMinBalance, globalV10TestProgram}, + 10: {GenesisHash, globalV10TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) require.Len(t, globalFieldSpecs, int(invalidGlobalField)) + // ensure we are testing everything + require.Equal(t, tests[AssemblerMaxVersion].lastField, invalidGlobalField-1, + "did you add a new global field?") + ledger := NewLedger(nil) addr, err := basics.UnmarshalChecksumAddress(testAddr) require.NoError(t, err) From b5ad8d0c9d040fce4c53cd5ee952ce8ef7ea6a2e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 11 Dec 2023 17:30:16 -0500 Subject: [PATCH 042/218] Docs: Eliminate a unicode character that causes trouble in doc generation (#5866) --- cmd/opdoc/opdoc.go | 2 +- data/transactions/logic/TEAL_opcodes_v1.md | 22 ++-- data/transactions/logic/TEAL_opcodes_v10.md | 132 ++++++++++---------- data/transactions/logic/TEAL_opcodes_v2.md | 36 +++--- data/transactions/logic/TEAL_opcodes_v3.md | 46 +++---- data/transactions/logic/TEAL_opcodes_v4.md | 54 ++++---- data/transactions/logic/TEAL_opcodes_v5.md | 80 ++++++------ data/transactions/logic/TEAL_opcodes_v6.md | 90 ++++++------- data/transactions/logic/TEAL_opcodes_v7.md | 100 +++++++-------- data/transactions/logic/TEAL_opcodes_v8.md | 120 +++++++++--------- data/transactions/logic/TEAL_opcodes_v9.md | 120 +++++++++--------- 11 files changed, 401 insertions(+), 401 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index aaa55c5d2d..8027c9a875 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -47,7 +47,7 @@ func opImmediateNoteSyntaxMarkdown(name string, oids []logic.OpImmediateDetails) argDocs[idx] = fmt.Sprintf("%s: %s", oid.Name, argNote) } - return fmt.Sprintf("`%s %s` ∋ %s", name, strings.Join(argNames, " "), strings.Join(argDocs, ", ")) + return fmt.Sprintf("`%s %s` where %s", name, strings.Join(argNames, " "), strings.Join(argDocs, ", ")) } func opImmediateNoteEncoding(opcode byte, oids []logic.OpImmediateDetails) string { diff --git a/data/transactions/logic/TEAL_opcodes_v1.md b/data/transactions/logic/TEAL_opcodes_v1.md index d98190643b..f255c4bec5 100644 --- a/data/transactions/logic/TEAL_opcodes_v1.md +++ b/data/transactions/logic/TEAL_opcodes_v1.md @@ -182,7 +182,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -191,7 +191,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -222,7 +222,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -231,7 +231,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -262,7 +262,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -298,7 +298,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -336,7 +336,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -356,7 +356,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -365,21 +365,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero diff --git a/data/transactions/logic/TEAL_opcodes_v10.md b/data/transactions/logic/TEAL_opcodes_v10.md index 81d742fbd4..83c6e9dfeb 100644 --- a/data/transactions/logic/TEAL_opcodes_v10.md +++ b/data/transactions/logic/TEAL_opcodes_v10.md @@ -41,7 +41,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte ## ecdsa_verify -- Syntax: `ecdsa_verify V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} - Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} @@ -62,7 +62,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced ## ecdsa_pk_decompress -- Syntax: `ecdsa_pk_decompress V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y @@ -73,7 +73,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top ## ecdsa_pk_recover -- Syntax: `ecdsa_pk_recover V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} - Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte - for (data A, recovery id B, signature C, D) recover a public key @@ -241,7 +241,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -250,7 +250,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -281,7 +281,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -290,7 +290,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -321,7 +321,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -357,7 +357,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -433,7 +433,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -466,7 +466,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -475,21 +475,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -512,7 +512,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -520,7 +520,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -530,7 +530,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -538,7 +538,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gload -- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group @@ -549,7 +549,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gloads -- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Syntax: `gloads I` where I: position in scratch space to load from - Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group @@ -560,7 +560,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaid -- Syntax: `gaid T` ∋ T: transaction group index +- Syntax: `gaid T` where T: transaction group index - Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group @@ -595,7 +595,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -606,7 +606,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -616,7 +616,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -640,7 +640,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## bury -- Syntax: `bury N` ∋ N: depth +- Syntax: `bury N` where N: depth - Bytecode: 0x45 {uint8} - Stack: ..., A → ... - replace the Nth value from the top of the stack with A. bury 0 fails. @@ -648,7 +648,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## popn -- Syntax: `popn N` ∋ N: stack depth +- Syntax: `popn N` where N: stack depth - Bytecode: 0x46 {uint8} - Stack: ..., [N items] → ... - remove N values from the top of the stack @@ -656,7 +656,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dupn -- Syntax: `dupn N` ∋ N: copy count +- Syntax: `dupn N` where N: copy count - Bytecode: 0x47 {uint8} - Stack: ..., A → ..., A, [N copies of A] - duplicate A, N times @@ -683,7 +683,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -705,7 +705,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## cover -- Syntax: `cover N` ∋ N: depth +- Syntax: `cover N` where N: depth - Bytecode: 0x4e {uint8} - Stack: ..., [N items], A → ..., A, [N items] - remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. @@ -713,7 +713,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## uncover -- Syntax: `uncover N` ∋ N: depth +- Syntax: `uncover N` where N: depth - Bytecode: 0x4f {uint8} - Stack: ..., A, [N items] → ..., [N items], A - remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. @@ -730,7 +730,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -777,7 +777,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## extract -- Syntax: `extract S L` ∋ S: start position, L: length +- Syntax: `extract S L` where S: start position, L: length - Bytecode: 0x57 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails @@ -813,7 +813,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## replace2 -- Syntax: `replace2 S` ∋ S: start position +- Syntax: `replace2 S` where S: start position - Bytecode: 0x5c {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)
`replace2` can be called using `replace` with 1 immediate. @@ -828,7 +828,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## base64_decode -- Syntax: `base64_decode E` ∋ E: [base64](#field-group-base64) +- Syntax: `base64_decode E` where E: [base64](#field-group-base64) - Bytecode: 0x5e {uint8} - Stack: ..., A: []byte → ..., []byte - decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E @@ -851,7 +851,7 @@ Encodings ## json_ref -- Syntax: `json_ref R` ∋ R: [json_ref](#field-group-json_ref) +- Syntax: `json_ref R` where R: [json_ref](#field-group-json_ref) - Bytecode: 0x5f {uint8} - Stack: ..., A: []byte, B: []byte → ..., any - key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A @@ -977,7 +977,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -998,7 +998,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -1029,7 +1029,7 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: ## app_params_get -- Syntax: `app_params_get F` ∋ F: [app_params](#field-group-app_params) +- Syntax: `app_params_get F` where F: [app_params](#field-group-app_params) - Bytecode: 0x72 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from app A. Y is 1 if A exists, else 0 @@ -1057,7 +1057,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag ## acct_params_get -- Syntax: `acct_params_get F` ∋ F: [acct_params](#field-group-acct_params) +- Syntax: `acct_params_get F` where F: [acct_params](#field-group-acct_params) - Bytecode: 0x73 {uint8} - Stack: ..., A → ..., X: any, Y: bool - X is field F from account A. Y is 1 if A owns positive algos, else 0 @@ -1096,7 +1096,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -1106,7 +1106,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT @@ -1116,7 +1116,7 @@ pushint args are not added to the intcblock during assembly processes ## pushbytess -- Syntax: `pushbytess BYTES ...` ∋ BYTES ...: a list of byte constants +- Syntax: `pushbytess BYTES ...` where BYTES ...: a list of byte constants - Bytecode: 0x82 {varuint count, [varuint length, bytes ...]} - Stack: ... → ..., [N items] - push sequences of immediate byte arrays to stack (first byte array being deepest) @@ -1126,7 +1126,7 @@ pushbytess args are not added to the bytecblock during assembly processes ## pushints -- Syntax: `pushints UINT ...` ∋ UINT ...: a list of int constants +- Syntax: `pushints UINT ...` where UINT ...: a list of int constants - Bytecode: 0x83 {varuint count, [varuint ...]} - Stack: ... → ..., [N items] - push sequence of immediate uints to stack in the order they appear (first uint being deepest) @@ -1144,7 +1144,7 @@ pushints args are not added to the intcblock during assembly processes ## callsub -- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Syntax: `callsub TARGET` where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack @@ -1163,7 +1163,7 @@ If the current frame was prepared by `proto A R`, `retsub` will remove the 'A' a ## proto -- Syntax: `proto A R` ∋ A: number of arguments, R: number of return values +- Syntax: `proto A R` where A: number of arguments, R: number of return values - Bytecode: 0x8a {uint8}, {uint8} - Stack: ... → ... - Prepare top call frame for a retsub that will assume A args and R return values. @@ -1173,7 +1173,7 @@ Fails unless the last instruction executed was a `callsub`. ## frame_dig -- Syntax: `frame_dig I` ∋ I: frame slot +- Syntax: `frame_dig I` where I: frame slot - Bytecode: 0x8b {int8} - Stack: ... → ..., any - Nth (signed) value from the frame pointer. @@ -1181,7 +1181,7 @@ Fails unless the last instruction executed was a `callsub`. ## frame_bury -- Syntax: `frame_bury I` ∋ I: frame slot +- Syntax: `frame_bury I` where I: frame slot - Bytecode: 0x8c {int8} - Stack: ..., A → ... - replace the Nth (signed) value from the frame pointer in the stack with A @@ -1189,7 +1189,7 @@ Fails unless the last instruction executed was a `callsub`. ## switch -- Syntax: `switch TARGET ...` ∋ TARGET ...: list of labels +- Syntax: `switch TARGET ...` where TARGET ...: list of labels - Bytecode: 0x8d {varuint count, [int16 (big-endian) ...]} - Stack: ..., A: uint64 → ... - branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. @@ -1197,7 +1197,7 @@ Fails unless the last instruction executed was a `callsub`. ## match -- Syntax: `match TARGET ...` ∋ TARGET ...: list of labels +- Syntax: `match TARGET ...` where TARGET ...: list of labels - Bytecode: 0x8e {varuint count, [int16 (big-endian) ...]} - Stack: ..., [A1, A2, ..., AN], B → ... - given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. @@ -1419,7 +1419,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_field -- Syntax: `itxn_field F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn_field F` where F: [txn](#field-group-txn) - Bytecode: 0xb2 {uint8} - Stack: ..., A → ... - set field F of the current inner transaction to A @@ -1440,7 +1440,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn -- Syntax: `itxn F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn F` where F: [txn](#field-group-txn) - Bytecode: 0xb4 {uint8} - Stack: ... → ..., any - field F of the last inner transaction @@ -1449,7 +1449,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxna -- Syntax: `itxna F I` ∋ F: [txna](#field-group-txna), I: a transaction field array index +- Syntax: `itxna F I` where F: [txna](#field-group-txna), I: a transaction field array index - Bytecode: 0xb5 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the last inner transaction @@ -1468,7 +1468,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxn -- Syntax: `gitxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gitxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0xb7 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the last inner group submitted @@ -1477,7 +1477,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxna -- Syntax: `gitxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gitxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0xb8 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the last inner group submitted @@ -1548,7 +1548,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## txnas -- Syntax: `txnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `txnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc0 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the current transaction @@ -1556,7 +1556,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gtxnas -- Syntax: `gtxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gtxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc1 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the current group @@ -1564,7 +1564,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gtxnsas -- Syntax: `gtxnsas F` ∋ F: [txna](#field-group-txna) +- Syntax: `gtxnsas F` where F: [txna](#field-group-txna) - Bytecode: 0xc2 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., any - Bth value of the array field F from the Ath transaction in the current group @@ -1588,7 +1588,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## itxnas -- Syntax: `itxnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `itxnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc5 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the last inner transaction @@ -1597,7 +1597,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gitxnas -- Syntax: `gitxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gitxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc6 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the last inner group submitted @@ -1606,7 +1606,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## vrf_verify -- Syntax: `vrf_verify S` ∋ S: [vrf_verify](#field-group-vrf_verify) +- Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} - Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. @@ -1626,7 +1626,7 @@ Standards ## block -- Syntax: `block F` ∋ F: [block](#field-group-block) +- Syntax: `block F` where F: [block](#field-group-block) - Bytecode: 0xd1 {uint8} - Stack: ..., A: uint64 → ..., any - field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) @@ -1662,7 +1662,7 @@ Boxes are of constant length. If C < len(D), then len(D)-C bytes will be removed ## ec_add -- Syntax: `ec_add G` ∋ G: [EC](#field-group-ec) +- Syntax: `ec_add G` where G: [EC](#field-group-ec) - Bytecode: 0xe0 {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - for curve points A and B, return the curve point A + B @@ -1694,7 +1694,7 @@ Does _not_ check if A and B are in the main prime-order subgroup. ## ec_scalar_mul -- Syntax: `ec_scalar_mul G` ∋ G: [EC](#field-group-ec) +- Syntax: `ec_scalar_mul G` where G: [EC](#field-group-ec) - Bytecode: 0xe1 {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - for curve point A and scalar B, return the curve point BA, the point A multiplied by the scalar B. @@ -1705,7 +1705,7 @@ A is a curve point encoded and checked as described in `ec_add`. Scalar B is int ## ec_pairing_check -- Syntax: `ec_pairing_check G` ∋ G: [EC](#field-group-ec) +- Syntax: `ec_pairing_check G` where G: [EC](#field-group-ec) - Bytecode: 0xe2 {uint8} - Stack: ..., A: []byte, B: []byte → ..., bool - 1 if the product of the pairing of each point in A with its respective point in B is equal to the identity element of the target group Gt, else 0 @@ -1716,7 +1716,7 @@ A and B are concatenated points, encoded and checked as described in `ec_add`. A ## ec_multi_scalar_mul -- Syntax: `ec_multi_scalar_mul G` ∋ G: [EC](#field-group-ec) +- Syntax: `ec_multi_scalar_mul G` where G: [EC](#field-group-ec) - Bytecode: 0xe3 {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - for curve points A and scalars B, return curve point B0A0 + B1A1 + B2A2 + ... + BnAn @@ -1728,7 +1728,7 @@ The name `ec_multi_scalar_mul` was chosen to reflect common usage, but a more co ## ec_subgroup_check -- Syntax: `ec_subgroup_check G` ∋ G: [EC](#field-group-ec) +- Syntax: `ec_subgroup_check G` where G: [EC](#field-group-ec) - Bytecode: 0xe4 {uint8} - Stack: ..., A: []byte → ..., bool - 1 if A is in the main prime-order subgroup of G (including the point at infinity) else 0. Program fails if A is not in G at all. @@ -1737,7 +1737,7 @@ The name `ec_multi_scalar_mul` was chosen to reflect common usage, but a more co ## ec_map_to -- Syntax: `ec_map_to G` ∋ G: [EC](#field-group-ec) +- Syntax: `ec_map_to G` where G: [EC](#field-group-ec) - Bytecode: 0xe5 {uint8} - Stack: ..., A: []byte → ..., []byte - maps field element A to group G diff --git a/data/transactions/logic/TEAL_opcodes_v2.md b/data/transactions/logic/TEAL_opcodes_v2.md index 180f899be8..0f0fa92488 100644 --- a/data/transactions/logic/TEAL_opcodes_v2.md +++ b/data/transactions/logic/TEAL_opcodes_v2.md @@ -189,7 +189,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -198,7 +198,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -229,7 +229,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -238,7 +238,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -269,7 +269,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -305,7 +305,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -365,7 +365,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -389,7 +389,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -398,21 +398,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -430,7 +430,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -438,7 +438,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -449,7 +449,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -459,7 +459,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -504,7 +504,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -621,7 +621,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -642,7 +642,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 diff --git a/data/transactions/logic/TEAL_opcodes_v3.md b/data/transactions/logic/TEAL_opcodes_v3.md index 9915f61027..b8e2c7e963 100644 --- a/data/transactions/logic/TEAL_opcodes_v3.md +++ b/data/transactions/logic/TEAL_opcodes_v3.md @@ -189,7 +189,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -198,7 +198,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -229,7 +229,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -238,7 +238,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -269,7 +269,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -305,7 +305,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -371,7 +371,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -396,7 +396,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -405,21 +405,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -439,7 +439,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -447,7 +447,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -457,7 +457,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -465,7 +465,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -476,7 +476,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -486,7 +486,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -529,7 +529,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -560,7 +560,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -709,7 +709,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -730,7 +730,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -770,7 +770,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -780,7 +780,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT diff --git a/data/transactions/logic/TEAL_opcodes_v4.md b/data/transactions/logic/TEAL_opcodes_v4.md index 3da0bcba03..66ebc2cc21 100644 --- a/data/transactions/logic/TEAL_opcodes_v4.md +++ b/data/transactions/logic/TEAL_opcodes_v4.md @@ -199,7 +199,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -208,7 +208,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -239,7 +239,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -248,7 +248,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -279,7 +279,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -315,7 +315,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -382,7 +382,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -407,7 +407,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -416,21 +416,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -450,7 +450,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -458,7 +458,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -468,7 +468,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -476,7 +476,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gload -- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group @@ -487,7 +487,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gloads -- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Syntax: `gloads I` where I: position in scratch space to load from - Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group @@ -498,7 +498,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaid -- Syntax: `gaid T` ∋ T: transaction group index +- Syntax: `gaid T` where T: transaction group index - Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group @@ -519,7 +519,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -530,7 +530,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -540,7 +540,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -583,7 +583,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -614,7 +614,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -763,7 +763,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -784,7 +784,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -824,7 +824,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -834,7 +834,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT @@ -844,7 +844,7 @@ pushint args are not added to the intcblock during assembly processes ## callsub -- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Syntax: `callsub TARGET` where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack diff --git a/data/transactions/logic/TEAL_opcodes_v5.md b/data/transactions/logic/TEAL_opcodes_v5.md index 584e8bc1f0..46c79662a0 100644 --- a/data/transactions/logic/TEAL_opcodes_v5.md +++ b/data/transactions/logic/TEAL_opcodes_v5.md @@ -41,7 +41,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte ## ecdsa_verify -- Syntax: `ecdsa_verify V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} - Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} @@ -61,7 +61,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced ## ecdsa_pk_decompress -- Syntax: `ecdsa_pk_decompress V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y @@ -72,7 +72,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top ## ecdsa_pk_recover -- Syntax: `ecdsa_pk_recover V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} - Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte - for (data A, recovery id B, signature C, D) recover a public key @@ -240,7 +240,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -249,7 +249,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -280,7 +280,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -289,7 +289,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -320,7 +320,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -356,7 +356,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -427,7 +427,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -454,7 +454,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -463,21 +463,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -498,7 +498,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -506,7 +506,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -516,7 +516,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -524,7 +524,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gload -- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group @@ -535,7 +535,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gloads -- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Syntax: `gloads I` where I: position in scratch space to load from - Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group @@ -546,7 +546,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaid -- Syntax: `gaid T` ∋ T: transaction group index +- Syntax: `gaid T` where T: transaction group index - Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group @@ -581,7 +581,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -592,7 +592,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -602,7 +602,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -645,7 +645,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -667,7 +667,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## cover -- Syntax: `cover N` ∋ N: depth +- Syntax: `cover N` where N: depth - Bytecode: 0x4e {uint8} - Stack: ..., [N items], A → ..., A, [N items] - remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. @@ -675,7 +675,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## uncover -- Syntax: `uncover N` ∋ N: depth +- Syntax: `uncover N` where N: depth - Bytecode: 0x4f {uint8} - Stack: ..., A, [N items] → ..., [N items], A - remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. @@ -692,7 +692,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -739,7 +739,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## extract -- Syntax: `extract S L` ∋ S: start position, L: length +- Syntax: `extract S L` where S: start position, L: length - Bytecode: 0x57 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails @@ -877,7 +877,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -898,7 +898,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -929,7 +929,7 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: ## app_params_get -- Syntax: `app_params_get F` ∋ F: [app_params](#field-group-app_params) +- Syntax: `app_params_get F` where F: [app_params](#field-group-app_params) - Bytecode: 0x72 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from app A. Y is 1 if A exists, else 0 @@ -967,7 +967,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -977,7 +977,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT @@ -987,7 +987,7 @@ pushint args are not added to the intcblock during assembly processes ## callsub -- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Syntax: `callsub TARGET` where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack @@ -1193,7 +1193,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## itxn_field -- Syntax: `itxn_field F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn_field F` where F: [txn](#field-group-txn) - Bytecode: 0xb2 {uint8} - Stack: ..., A → ... - set field F of the current inner transaction to A @@ -1214,7 +1214,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## itxn -- Syntax: `itxn F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn F` where F: [txn](#field-group-txn) - Bytecode: 0xb4 {uint8} - Stack: ... → ..., any - field F of the last inner transaction @@ -1223,7 +1223,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## itxna -- Syntax: `itxna F I` ∋ F: [txna](#field-group-txna), I: a transaction field array index +- Syntax: `itxna F I` where F: [txna](#field-group-txna), I: a transaction field array index - Bytecode: 0xb5 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the last inner transaction @@ -1232,7 +1232,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## txnas -- Syntax: `txnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `txnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc0 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the current transaction @@ -1240,7 +1240,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## gtxnas -- Syntax: `gtxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gtxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc1 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the current group @@ -1248,7 +1248,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## gtxnsas -- Syntax: `gtxnsas F` ∋ F: [txna](#field-group-txna) +- Syntax: `gtxnsas F` where F: [txna](#field-group-txna) - Bytecode: 0xc2 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., any - Bth value of the array field F from the Ath transaction in the current group diff --git a/data/transactions/logic/TEAL_opcodes_v6.md b/data/transactions/logic/TEAL_opcodes_v6.md index eab29a84cb..56d79cd072 100644 --- a/data/transactions/logic/TEAL_opcodes_v6.md +++ b/data/transactions/logic/TEAL_opcodes_v6.md @@ -41,7 +41,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte ## ecdsa_verify -- Syntax: `ecdsa_verify V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} - Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} @@ -61,7 +61,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced ## ecdsa_pk_decompress -- Syntax: `ecdsa_pk_decompress V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y @@ -72,7 +72,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top ## ecdsa_pk_recover -- Syntax: `ecdsa_pk_recover V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} - Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte - for (data A, recovery id B, signature C, D) recover a public key @@ -240,7 +240,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -249,7 +249,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -280,7 +280,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -289,7 +289,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -320,7 +320,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -356,7 +356,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -429,7 +429,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -459,7 +459,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -468,21 +468,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -503,7 +503,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -511,7 +511,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -521,7 +521,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -529,7 +529,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gload -- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group @@ -540,7 +540,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gloads -- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Syntax: `gloads I` where I: position in scratch space to load from - Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group @@ -551,7 +551,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaid -- Syntax: `gaid T` ∋ T: transaction group index +- Syntax: `gaid T` where T: transaction group index - Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group @@ -586,7 +586,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -597,7 +597,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -607,7 +607,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -650,7 +650,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -672,7 +672,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## cover -- Syntax: `cover N` ∋ N: depth +- Syntax: `cover N` where N: depth - Bytecode: 0x4e {uint8} - Stack: ..., [N items], A → ..., A, [N items] - remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. @@ -680,7 +680,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## uncover -- Syntax: `uncover N` ∋ N: depth +- Syntax: `uncover N` where N: depth - Bytecode: 0x4f {uint8} - Stack: ..., A, [N items] → ..., [N items], A - remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. @@ -697,7 +697,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -744,7 +744,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## extract -- Syntax: `extract S L` ∋ S: start position, L: length +- Syntax: `extract S L` where S: start position, L: length - Bytecode: 0x57 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails @@ -882,7 +882,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -903,7 +903,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -934,7 +934,7 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: ## app_params_get -- Syntax: `app_params_get F` ∋ F: [app_params](#field-group-app_params) +- Syntax: `app_params_get F` where F: [app_params](#field-group-app_params) - Bytecode: 0x72 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from app A. Y is 1 if A exists, else 0 @@ -962,7 +962,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag ## acct_params_get -- Syntax: `acct_params_get F` ∋ F: [acct_params](#field-group-acct_params) +- Syntax: `acct_params_get F` where F: [acct_params](#field-group-acct_params) - Bytecode: 0x73 {uint8} - Stack: ..., A → ..., X: any, Y: bool - X is field F from account A. Y is 1 if A owns positive algos, else 0 @@ -992,7 +992,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -1002,7 +1002,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT @@ -1012,7 +1012,7 @@ pushint args are not added to the intcblock during assembly processes ## callsub -- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Syntax: `callsub TARGET` where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack @@ -1235,7 +1235,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_field -- Syntax: `itxn_field F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn_field F` where F: [txn](#field-group-txn) - Bytecode: 0xb2 {uint8} - Stack: ..., A → ... - set field F of the current inner transaction to A @@ -1256,7 +1256,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn -- Syntax: `itxn F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn F` where F: [txn](#field-group-txn) - Bytecode: 0xb4 {uint8} - Stack: ... → ..., any - field F of the last inner transaction @@ -1265,7 +1265,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxna -- Syntax: `itxna F I` ∋ F: [txna](#field-group-txna), I: a transaction field array index +- Syntax: `itxna F I` where F: [txna](#field-group-txna), I: a transaction field array index - Bytecode: 0xb5 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the last inner transaction @@ -1284,7 +1284,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxn -- Syntax: `gitxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gitxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0xb7 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the last inner group submitted @@ -1293,7 +1293,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxna -- Syntax: `gitxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gitxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0xb8 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the last inner group submitted @@ -1302,7 +1302,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## txnas -- Syntax: `txnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `txnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc0 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the current transaction @@ -1310,7 +1310,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gtxnas -- Syntax: `gtxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gtxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc1 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the current group @@ -1318,7 +1318,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gtxnsas -- Syntax: `gtxnsas F` ∋ F: [txna](#field-group-txna) +- Syntax: `gtxnsas F` where F: [txna](#field-group-txna) - Bytecode: 0xc2 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., any - Bth value of the array field F from the Ath transaction in the current group @@ -1342,7 +1342,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxnas -- Syntax: `itxnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `itxnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc5 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the last inner transaction @@ -1351,7 +1351,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxnas -- Syntax: `gitxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gitxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc6 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the last inner group submitted diff --git a/data/transactions/logic/TEAL_opcodes_v7.md b/data/transactions/logic/TEAL_opcodes_v7.md index 5eb79e38f3..3536f35f8a 100644 --- a/data/transactions/logic/TEAL_opcodes_v7.md +++ b/data/transactions/logic/TEAL_opcodes_v7.md @@ -41,7 +41,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte ## ecdsa_verify -- Syntax: `ecdsa_verify V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} - Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} @@ -62,7 +62,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced ## ecdsa_pk_decompress -- Syntax: `ecdsa_pk_decompress V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y @@ -73,7 +73,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top ## ecdsa_pk_recover -- Syntax: `ecdsa_pk_recover V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} - Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte - for (data A, recovery id B, signature C, D) recover a public key @@ -241,7 +241,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -250,7 +250,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -281,7 +281,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -290,7 +290,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -321,7 +321,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -357,7 +357,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -433,7 +433,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -463,7 +463,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -472,21 +472,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -509,7 +509,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -517,7 +517,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -527,7 +527,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -535,7 +535,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gload -- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group @@ -546,7 +546,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gloads -- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Syntax: `gloads I` where I: position in scratch space to load from - Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group @@ -557,7 +557,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaid -- Syntax: `gaid T` ∋ T: transaction group index +- Syntax: `gaid T` where T: transaction group index - Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group @@ -592,7 +592,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -603,7 +603,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -613,7 +613,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -656,7 +656,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -678,7 +678,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## cover -- Syntax: `cover N` ∋ N: depth +- Syntax: `cover N` where N: depth - Bytecode: 0x4e {uint8} - Stack: ..., [N items], A → ..., A, [N items] - remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. @@ -686,7 +686,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## uncover -- Syntax: `uncover N` ∋ N: depth +- Syntax: `uncover N` where N: depth - Bytecode: 0x4f {uint8} - Stack: ..., A, [N items] → ..., [N items], A - remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. @@ -703,7 +703,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -750,7 +750,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## extract -- Syntax: `extract S L` ∋ S: start position, L: length +- Syntax: `extract S L` where S: start position, L: length - Bytecode: 0x57 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails @@ -786,7 +786,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## replace2 -- Syntax: `replace2 S` ∋ S: start position +- Syntax: `replace2 S` where S: start position - Bytecode: 0x5c {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)
`replace2` can be called using `replace` with 1 immediate. @@ -801,7 +801,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## base64_decode -- Syntax: `base64_decode E` ∋ E: [base64](#field-group-base64) +- Syntax: `base64_decode E` where E: [base64](#field-group-base64) - Bytecode: 0x5e {uint8} - Stack: ..., A: []byte → ..., []byte - decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E @@ -824,7 +824,7 @@ Encodings ## json_ref -- Syntax: `json_ref R` ∋ R: [json_ref](#field-group-json_ref) +- Syntax: `json_ref R` where R: [json_ref](#field-group-json_ref) - Bytecode: 0x5f {uint8} - Stack: ..., A: []byte, B: []byte → ..., any - key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A @@ -950,7 +950,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -971,7 +971,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -1002,7 +1002,7 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: ## app_params_get -- Syntax: `app_params_get F` ∋ F: [app_params](#field-group-app_params) +- Syntax: `app_params_get F` where F: [app_params](#field-group-app_params) - Bytecode: 0x72 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from app A. Y is 1 if A exists, else 0 @@ -1030,7 +1030,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag ## acct_params_get -- Syntax: `acct_params_get F` ∋ F: [acct_params](#field-group-acct_params) +- Syntax: `acct_params_get F` where F: [acct_params](#field-group-acct_params) - Bytecode: 0x73 {uint8} - Stack: ..., A → ..., X: any, Y: bool - X is field F from account A. Y is 1 if A owns positive algos, else 0 @@ -1060,7 +1060,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -1070,7 +1070,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT @@ -1088,7 +1088,7 @@ pushint args are not added to the intcblock during assembly processes ## callsub -- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Syntax: `callsub TARGET` where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack @@ -1319,7 +1319,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_field -- Syntax: `itxn_field F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn_field F` where F: [txn](#field-group-txn) - Bytecode: 0xb2 {uint8} - Stack: ..., A → ... - set field F of the current inner transaction to A @@ -1340,7 +1340,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn -- Syntax: `itxn F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn F` where F: [txn](#field-group-txn) - Bytecode: 0xb4 {uint8} - Stack: ... → ..., any - field F of the last inner transaction @@ -1349,7 +1349,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxna -- Syntax: `itxna F I` ∋ F: [txna](#field-group-txna), I: a transaction field array index +- Syntax: `itxna F I` where F: [txna](#field-group-txna), I: a transaction field array index - Bytecode: 0xb5 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the last inner transaction @@ -1368,7 +1368,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxn -- Syntax: `gitxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gitxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0xb7 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the last inner group submitted @@ -1377,7 +1377,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxna -- Syntax: `gitxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gitxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0xb8 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the last inner group submitted @@ -1386,7 +1386,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## txnas -- Syntax: `txnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `txnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc0 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the current transaction @@ -1394,7 +1394,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gtxnas -- Syntax: `gtxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gtxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc1 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the current group @@ -1402,7 +1402,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gtxnsas -- Syntax: `gtxnsas F` ∋ F: [txna](#field-group-txna) +- Syntax: `gtxnsas F` where F: [txna](#field-group-txna) - Bytecode: 0xc2 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., any - Bth value of the array field F from the Ath transaction in the current group @@ -1426,7 +1426,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxnas -- Syntax: `itxnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `itxnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc5 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the last inner transaction @@ -1435,7 +1435,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxnas -- Syntax: `gitxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gitxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc6 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the last inner group submitted @@ -1444,7 +1444,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## vrf_verify -- Syntax: `vrf_verify S` ∋ S: [vrf_verify](#field-group-vrf_verify) +- Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} - Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. @@ -1464,7 +1464,7 @@ Standards ## block -- Syntax: `block F` ∋ F: [block](#field-group-block) +- Syntax: `block F` where F: [block](#field-group-block) - Bytecode: 0xd1 {uint8} - Stack: ..., A: uint64 → ..., any - field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) diff --git a/data/transactions/logic/TEAL_opcodes_v8.md b/data/transactions/logic/TEAL_opcodes_v8.md index 71d756a1dc..b24a145086 100644 --- a/data/transactions/logic/TEAL_opcodes_v8.md +++ b/data/transactions/logic/TEAL_opcodes_v8.md @@ -41,7 +41,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte ## ecdsa_verify -- Syntax: `ecdsa_verify V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} - Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} @@ -62,7 +62,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced ## ecdsa_pk_decompress -- Syntax: `ecdsa_pk_decompress V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y @@ -73,7 +73,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top ## ecdsa_pk_recover -- Syntax: `ecdsa_pk_recover V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} - Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte - for (data A, recovery id B, signature C, D) recover a public key @@ -241,7 +241,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -250,7 +250,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -281,7 +281,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -290,7 +290,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -321,7 +321,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -357,7 +357,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -433,7 +433,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -463,7 +463,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -472,21 +472,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -509,7 +509,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -517,7 +517,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -527,7 +527,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -535,7 +535,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gload -- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group @@ -546,7 +546,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gloads -- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Syntax: `gloads I` where I: position in scratch space to load from - Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group @@ -557,7 +557,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaid -- Syntax: `gaid T` ∋ T: transaction group index +- Syntax: `gaid T` where T: transaction group index - Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group @@ -592,7 +592,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -603,7 +603,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -613,7 +613,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -637,7 +637,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## bury -- Syntax: `bury N` ∋ N: depth +- Syntax: `bury N` where N: depth - Bytecode: 0x45 {uint8} - Stack: ..., A → ... - replace the Nth value from the top of the stack with A. bury 0 fails. @@ -645,7 +645,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## popn -- Syntax: `popn N` ∋ N: stack depth +- Syntax: `popn N` where N: stack depth - Bytecode: 0x46 {uint8} - Stack: ..., [N items] → ... - remove N values from the top of the stack @@ -653,7 +653,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dupn -- Syntax: `dupn N` ∋ N: copy count +- Syntax: `dupn N` where N: copy count - Bytecode: 0x47 {uint8} - Stack: ..., A → ..., A, [N copies of A] - duplicate A, N times @@ -680,7 +680,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -702,7 +702,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## cover -- Syntax: `cover N` ∋ N: depth +- Syntax: `cover N` where N: depth - Bytecode: 0x4e {uint8} - Stack: ..., [N items], A → ..., A, [N items] - remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. @@ -710,7 +710,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## uncover -- Syntax: `uncover N` ∋ N: depth +- Syntax: `uncover N` where N: depth - Bytecode: 0x4f {uint8} - Stack: ..., A, [N items] → ..., [N items], A - remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. @@ -727,7 +727,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -774,7 +774,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## extract -- Syntax: `extract S L` ∋ S: start position, L: length +- Syntax: `extract S L` where S: start position, L: length - Bytecode: 0x57 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails @@ -810,7 +810,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## replace2 -- Syntax: `replace2 S` ∋ S: start position +- Syntax: `replace2 S` where S: start position - Bytecode: 0x5c {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)
`replace2` can be called using `replace` with 1 immediate. @@ -825,7 +825,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## base64_decode -- Syntax: `base64_decode E` ∋ E: [base64](#field-group-base64) +- Syntax: `base64_decode E` where E: [base64](#field-group-base64) - Bytecode: 0x5e {uint8} - Stack: ..., A: []byte → ..., []byte - decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E @@ -848,7 +848,7 @@ Encodings ## json_ref -- Syntax: `json_ref R` ∋ R: [json_ref](#field-group-json_ref) +- Syntax: `json_ref R` where R: [json_ref](#field-group-json_ref) - Bytecode: 0x5f {uint8} - Stack: ..., A: []byte, B: []byte → ..., any - key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A @@ -974,7 +974,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -995,7 +995,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -1026,7 +1026,7 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: ## app_params_get -- Syntax: `app_params_get F` ∋ F: [app_params](#field-group-app_params) +- Syntax: `app_params_get F` where F: [app_params](#field-group-app_params) - Bytecode: 0x72 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from app A. Y is 1 if A exists, else 0 @@ -1054,7 +1054,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag ## acct_params_get -- Syntax: `acct_params_get F` ∋ F: [acct_params](#field-group-acct_params) +- Syntax: `acct_params_get F` where F: [acct_params](#field-group-acct_params) - Bytecode: 0x73 {uint8} - Stack: ..., A → ..., X: any, Y: bool - X is field F from account A. Y is 1 if A owns positive algos, else 0 @@ -1093,7 +1093,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -1103,7 +1103,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT @@ -1113,7 +1113,7 @@ pushint args are not added to the intcblock during assembly processes ## pushbytess -- Syntax: `pushbytess BYTES ...` ∋ BYTES ...: a list of byte constants +- Syntax: `pushbytess BYTES ...` where BYTES ...: a list of byte constants - Bytecode: 0x82 {varuint count, [varuint length, bytes ...]} - Stack: ... → ..., [N items] - push sequences of immediate byte arrays to stack (first byte array being deepest) @@ -1123,7 +1123,7 @@ pushbytess args are not added to the bytecblock during assembly processes ## pushints -- Syntax: `pushints UINT ...` ∋ UINT ...: a list of int constants +- Syntax: `pushints UINT ...` where UINT ...: a list of int constants - Bytecode: 0x83 {varuint count, [varuint ...]} - Stack: ... → ..., [N items] - push sequence of immediate uints to stack in the order they appear (first uint being deepest) @@ -1141,7 +1141,7 @@ pushints args are not added to the intcblock during assembly processes ## callsub -- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Syntax: `callsub TARGET` where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack @@ -1160,7 +1160,7 @@ If the current frame was prepared by `proto A R`, `retsub` will remove the 'A' a ## proto -- Syntax: `proto A R` ∋ A: number of arguments, R: number of return values +- Syntax: `proto A R` where A: number of arguments, R: number of return values - Bytecode: 0x8a {uint8}, {uint8} - Stack: ... → ... - Prepare top call frame for a retsub that will assume A args and R return values. @@ -1170,7 +1170,7 @@ Fails unless the last instruction executed was a `callsub`. ## frame_dig -- Syntax: `frame_dig I` ∋ I: frame slot +- Syntax: `frame_dig I` where I: frame slot - Bytecode: 0x8b {int8} - Stack: ... → ..., any - Nth (signed) value from the frame pointer. @@ -1178,7 +1178,7 @@ Fails unless the last instruction executed was a `callsub`. ## frame_bury -- Syntax: `frame_bury I` ∋ I: frame slot +- Syntax: `frame_bury I` where I: frame slot - Bytecode: 0x8c {int8} - Stack: ..., A → ... - replace the Nth (signed) value from the frame pointer in the stack with A @@ -1186,7 +1186,7 @@ Fails unless the last instruction executed was a `callsub`. ## switch -- Syntax: `switch TARGET ...` ∋ TARGET ...: list of labels +- Syntax: `switch TARGET ...` where TARGET ...: list of labels - Bytecode: 0x8d {varuint count, [int16 (big-endian) ...]} - Stack: ..., A: uint64 → ... - branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. @@ -1194,7 +1194,7 @@ Fails unless the last instruction executed was a `callsub`. ## match -- Syntax: `match TARGET ...` ∋ TARGET ...: list of labels +- Syntax: `match TARGET ...` where TARGET ...: list of labels - Bytecode: 0x8e {varuint count, [int16 (big-endian) ...]} - Stack: ..., [A1, A2, ..., AN], B → ... - given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. @@ -1416,7 +1416,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_field -- Syntax: `itxn_field F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn_field F` where F: [txn](#field-group-txn) - Bytecode: 0xb2 {uint8} - Stack: ..., A → ... - set field F of the current inner transaction to A @@ -1437,7 +1437,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn -- Syntax: `itxn F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn F` where F: [txn](#field-group-txn) - Bytecode: 0xb4 {uint8} - Stack: ... → ..., any - field F of the last inner transaction @@ -1446,7 +1446,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxna -- Syntax: `itxna F I` ∋ F: [txna](#field-group-txna), I: a transaction field array index +- Syntax: `itxna F I` where F: [txna](#field-group-txna), I: a transaction field array index - Bytecode: 0xb5 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the last inner transaction @@ -1465,7 +1465,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxn -- Syntax: `gitxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gitxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0xb7 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the last inner group submitted @@ -1474,7 +1474,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxna -- Syntax: `gitxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gitxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0xb8 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the last inner group submitted @@ -1545,7 +1545,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## txnas -- Syntax: `txnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `txnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc0 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the current transaction @@ -1553,7 +1553,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gtxnas -- Syntax: `gtxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gtxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc1 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the current group @@ -1561,7 +1561,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gtxnsas -- Syntax: `gtxnsas F` ∋ F: [txna](#field-group-txna) +- Syntax: `gtxnsas F` where F: [txna](#field-group-txna) - Bytecode: 0xc2 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., any - Bth value of the array field F from the Ath transaction in the current group @@ -1585,7 +1585,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## itxnas -- Syntax: `itxnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `itxnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc5 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the last inner transaction @@ -1594,7 +1594,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gitxnas -- Syntax: `gitxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gitxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc6 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the last inner group submitted @@ -1603,7 +1603,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## vrf_verify -- Syntax: `vrf_verify S` ∋ S: [vrf_verify](#field-group-vrf_verify) +- Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} - Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. @@ -1623,7 +1623,7 @@ Standards ## block -- Syntax: `block F` ∋ F: [block](#field-group-block) +- Syntax: `block F` where F: [block](#field-group-block) - Bytecode: 0xd1 {uint8} - Stack: ..., A: uint64 → ..., any - field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) diff --git a/data/transactions/logic/TEAL_opcodes_v9.md b/data/transactions/logic/TEAL_opcodes_v9.md index f2ff330591..f64d98931c 100644 --- a/data/transactions/logic/TEAL_opcodes_v9.md +++ b/data/transactions/logic/TEAL_opcodes_v9.md @@ -41,7 +41,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte ## ecdsa_verify -- Syntax: `ecdsa_verify V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} - Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} @@ -62,7 +62,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced ## ecdsa_pk_decompress -- Syntax: `ecdsa_pk_decompress V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y @@ -73,7 +73,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top ## ecdsa_pk_recover -- Syntax: `ecdsa_pk_recover V` ∋ V: [ECDSA](#field-group-ecdsa) +- Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} - Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte - for (data A, recovery id B, signature C, D) recover a public key @@ -241,7 +241,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intcblock -- Syntax: `intcblock UINT ...` ∋ UINT ...: a block of int constant values +- Syntax: `intcblock UINT ...` where UINT ...: a block of int constant values - Bytecode: 0x20 {varuint count, [varuint ...]} - Stack: ... → ... - prepare block of uint64 constants for use by intc @@ -250,7 +250,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## intc -- Syntax: `intc I` ∋ I: an index in the intcblock +- Syntax: `intc I` where I: an index in the intcblock - Bytecode: 0x21 {uint8} - Stack: ... → ..., uint64 - Ith constant from intcblock @@ -281,7 +281,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytecblock -- Syntax: `bytecblock BYTES ...` ∋ BYTES ...: a block of byte constant values +- Syntax: `bytecblock BYTES ...` where BYTES ...: a block of byte constant values - Bytecode: 0x26 {varuint count, [varuint length, bytes ...]} - Stack: ... → ... - prepare block of byte-array constants for use by bytec @@ -290,7 +290,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## bytec -- Syntax: `bytec I` ∋ I: an index in the bytecblock +- Syntax: `bytec I` where I: an index in the bytecblock - Bytecode: 0x27 {uint8} - Stack: ... → ..., []byte - Ith constant from bytecblock @@ -321,7 +321,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## arg -- Syntax: `arg N` ∋ N: an arg index +- Syntax: `arg N` where N: an arg index - Bytecode: 0x2c {uint8} - Stack: ... → ..., []byte - Nth LogicSig argument @@ -357,7 +357,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u ## txn -- Syntax: `txn F` ∋ F: [txn](#field-group-txn) +- Syntax: `txn F` where F: [txn](#field-group-txn) - Bytecode: 0x31 {uint8} - Stack: ... → ..., any - field F of current transaction @@ -433,7 +433,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## global -- Syntax: `global F` ∋ F: [global](#field-group-global) +- Syntax: `global F` where F: [global](#field-group-global) - Bytecode: 0x32 {uint8} - Stack: ... → ..., any - global field F @@ -463,7 +463,7 @@ Fields ## gtxn -- Syntax: `gtxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gtxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0x33 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the current group @@ -472,21 +472,21 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ ## load -- Syntax: `load I` ∋ I: position in scratch space to load from +- Syntax: `load I` where I: position in scratch space to load from - Bytecode: 0x34 {uint8} - Stack: ... → ..., any - Ith scratch space value. All scratch spaces are 0 at program start. ## store -- Syntax: `store I` ∋ I: position in scratch space to store to +- Syntax: `store I` where I: position in scratch space to store to - Bytecode: 0x35 {uint8} - Stack: ..., A → ... - store A to the Ith scratch space ## txna -- Syntax: `txna F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `txna F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x36 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the current transaction
`txna` can be called using `txn` with 2 immediates. @@ -509,7 +509,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxna -- Syntax: `gtxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x37 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the current group
`gtxna` can be called using `gtxn` with 3 immediates. @@ -517,7 +517,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc ## gtxns -- Syntax: `gtxns F` ∋ F: [txn](#field-group-txn) +- Syntax: `gtxns F` where F: [txn](#field-group-txn) - Bytecode: 0x38 {uint8} - Stack: ..., A: uint64 → ..., any - field F of the Ath transaction in the current group @@ -527,7 +527,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gtxnsa -- Syntax: `gtxnsa F I` ∋ F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gtxnsa F I` where F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0x39 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ith value of the array field F from the Ath transaction in the current group
`gtxnsa` can be called using `gtxns` with 2 immediates. @@ -535,7 +535,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gload -- Syntax: `gload T I` ∋ T: transaction group index, I: position in scratch space to load from +- Syntax: `gload T I` where T: transaction group index, I: position in scratch space to load from - Bytecode: 0x3a {uint8}, {uint8} - Stack: ... → ..., any - Ith scratch space value of the Tth transaction in the current group @@ -546,7 +546,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gloads -- Syntax: `gloads I` ∋ I: position in scratch space to load from +- Syntax: `gloads I` where I: position in scratch space to load from - Bytecode: 0x3b {uint8} - Stack: ..., A: uint64 → ..., any - Ith scratch space value of the Ath transaction in the current group @@ -557,7 +557,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## gaid -- Syntax: `gaid T` ∋ T: transaction group index +- Syntax: `gaid T` where T: transaction group index - Bytecode: 0x3c {uint8} - Stack: ... → ..., uint64 - ID of the asset or application created in the Tth transaction of the current group @@ -592,7 +592,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g ## bnz -- Syntax: `bnz TARGET` ∋ TARGET: branch offset +- Syntax: `bnz TARGET` where TARGET: branch offset - Bytecode: 0x40 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is not zero @@ -603,7 +603,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la ## bz -- Syntax: `bz TARGET` ∋ TARGET: branch offset +- Syntax: `bz TARGET` where TARGET: branch offset - Bytecode: 0x41 {int16 (big-endian)} - Stack: ..., A: uint64 → ... - branch to TARGET if value A is zero @@ -613,7 +613,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`. ## b -- Syntax: `b TARGET` ∋ TARGET: branch offset +- Syntax: `b TARGET` where TARGET: branch offset - Bytecode: 0x42 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET @@ -637,7 +637,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## bury -- Syntax: `bury N` ∋ N: depth +- Syntax: `bury N` where N: depth - Bytecode: 0x45 {uint8} - Stack: ..., A → ... - replace the Nth value from the top of the stack with A. bury 0 fails. @@ -645,7 +645,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## popn -- Syntax: `popn N` ∋ N: stack depth +- Syntax: `popn N` where N: stack depth - Bytecode: 0x46 {uint8} - Stack: ..., [N items] → ... - remove N values from the top of the stack @@ -653,7 +653,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dupn -- Syntax: `dupn N` ∋ N: copy count +- Syntax: `dupn N` where N: copy count - Bytecode: 0x47 {uint8} - Stack: ..., A → ..., A, [N copies of A] - duplicate A, N times @@ -680,7 +680,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## dig -- Syntax: `dig N` ∋ N: depth +- Syntax: `dig N` where N: depth - Bytecode: 0x4b {uint8} - Stack: ..., A, [N items] → ..., A, [N items], A - Nth value from the top of the stack. dig 0 is equivalent to dup @@ -702,7 +702,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## cover -- Syntax: `cover N` ∋ N: depth +- Syntax: `cover N` where N: depth - Bytecode: 0x4e {uint8} - Stack: ..., [N items], A → ..., A, [N items] - remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. @@ -710,7 +710,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## uncover -- Syntax: `uncover N` ∋ N: depth +- Syntax: `uncover N` where N: depth - Bytecode: 0x4f {uint8} - Stack: ..., A, [N items] → ..., [N items], A - remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. @@ -727,7 +727,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. ## substring -- Syntax: `substring S E` ∋ S: start position, E: end position +- Syntax: `substring S E` where S: start position, E: end position - Bytecode: 0x51 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including E. If E < S, or either is larger than the array length, the program fails @@ -774,7 +774,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## extract -- Syntax: `extract S L` ∋ S: start position, L: length +- Syntax: `extract S L` where S: start position, L: length - Bytecode: 0x57 {uint8}, {uint8} - Stack: ..., A: []byte → ..., []byte - A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails @@ -810,7 +810,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## replace2 -- Syntax: `replace2 S` ∋ S: start position +- Syntax: `replace2 S` where S: start position - Bytecode: 0x5c {uint8} - Stack: ..., A: []byte, B: []byte → ..., []byte - Copy of A with the bytes starting at S replaced by the bytes of B. Fails if S+len(B) exceeds len(A)
`replace2` can be called using `replace` with 1 immediate. @@ -825,7 +825,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on ## base64_decode -- Syntax: `base64_decode E` ∋ E: [base64](#field-group-base64) +- Syntax: `base64_decode E` where E: [base64](#field-group-base64) - Bytecode: 0x5e {uint8} - Stack: ..., A: []byte → ..., []byte - decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E @@ -848,7 +848,7 @@ Encodings ## json_ref -- Syntax: `json_ref R` ∋ R: [json_ref](#field-group-json_ref) +- Syntax: `json_ref R` where R: [json_ref](#field-group-json_ref) - Bytecode: 0x5f {uint8} - Stack: ..., A: []byte, B: []byte → ..., any - key B's value, of type R, from a [valid](jsonspec.md) utf-8 encoded json object A @@ -974,7 +974,7 @@ Deleting a key which is already absent has no effect on the application global s ## asset_holding_get -- Syntax: `asset_holding_get F` ∋ F: [asset_holding](#field-group-asset_holding) +- Syntax: `asset_holding_get F` where F: [asset_holding](#field-group-asset_holding) - Bytecode: 0x70 {uint8} - Stack: ..., A, B: uint64 → ..., X: any, Y: bool - X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0 @@ -995,7 +995,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or ## asset_params_get -- Syntax: `asset_params_get F` ∋ F: [asset_params](#field-group-asset_params) +- Syntax: `asset_params_get F` where F: [asset_params](#field-group-asset_params) - Bytecode: 0x71 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from asset A. Y is 1 if A exists, else 0 @@ -1026,7 +1026,7 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: ## app_params_get -- Syntax: `app_params_get F` ∋ F: [app_params](#field-group-app_params) +- Syntax: `app_params_get F` where F: [app_params](#field-group-app_params) - Bytecode: 0x72 {uint8} - Stack: ..., A: uint64 → ..., X: any, Y: bool - X is field F from app A. Y is 1 if A exists, else 0 @@ -1054,7 +1054,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag ## acct_params_get -- Syntax: `acct_params_get F` ∋ F: [acct_params](#field-group-acct_params) +- Syntax: `acct_params_get F` where F: [acct_params](#field-group-acct_params) - Bytecode: 0x73 {uint8} - Stack: ..., A → ..., X: any, Y: bool - X is field F from account A. Y is 1 if A owns positive algos, else 0 @@ -1093,7 +1093,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## pushbytes -- Syntax: `pushbytes BYTES` ∋ BYTES: a byte constant +- Syntax: `pushbytes BYTES` where BYTES: a byte constant - Bytecode: 0x80 {varuint length, bytes} - Stack: ... → ..., []byte - immediate BYTES @@ -1103,7 +1103,7 @@ pushbytes args are not added to the bytecblock during assembly processes ## pushint -- Syntax: `pushint UINT` ∋ UINT: an int constant +- Syntax: `pushint UINT` where UINT: an int constant - Bytecode: 0x81 {varuint} - Stack: ... → ..., uint64 - immediate UINT @@ -1113,7 +1113,7 @@ pushint args are not added to the intcblock during assembly processes ## pushbytess -- Syntax: `pushbytess BYTES ...` ∋ BYTES ...: a list of byte constants +- Syntax: `pushbytess BYTES ...` where BYTES ...: a list of byte constants - Bytecode: 0x82 {varuint count, [varuint length, bytes ...]} - Stack: ... → ..., [N items] - push sequences of immediate byte arrays to stack (first byte array being deepest) @@ -1123,7 +1123,7 @@ pushbytess args are not added to the bytecblock during assembly processes ## pushints -- Syntax: `pushints UINT ...` ∋ UINT ...: a list of int constants +- Syntax: `pushints UINT ...` where UINT ...: a list of int constants - Bytecode: 0x83 {varuint count, [varuint ...]} - Stack: ... → ..., [N items] - push sequence of immediate uints to stack in the order they appear (first uint being deepest) @@ -1141,7 +1141,7 @@ pushints args are not added to the intcblock during assembly processes ## callsub -- Syntax: `callsub TARGET` ∋ TARGET: branch offset +- Syntax: `callsub TARGET` where TARGET: branch offset - Bytecode: 0x88 {int16 (big-endian)} - Stack: ... → ... - branch unconditionally to TARGET, saving the next instruction on the call stack @@ -1160,7 +1160,7 @@ If the current frame was prepared by `proto A R`, `retsub` will remove the 'A' a ## proto -- Syntax: `proto A R` ∋ A: number of arguments, R: number of return values +- Syntax: `proto A R` where A: number of arguments, R: number of return values - Bytecode: 0x8a {uint8}, {uint8} - Stack: ... → ... - Prepare top call frame for a retsub that will assume A args and R return values. @@ -1170,7 +1170,7 @@ Fails unless the last instruction executed was a `callsub`. ## frame_dig -- Syntax: `frame_dig I` ∋ I: frame slot +- Syntax: `frame_dig I` where I: frame slot - Bytecode: 0x8b {int8} - Stack: ... → ..., any - Nth (signed) value from the frame pointer. @@ -1178,7 +1178,7 @@ Fails unless the last instruction executed was a `callsub`. ## frame_bury -- Syntax: `frame_bury I` ∋ I: frame slot +- Syntax: `frame_bury I` where I: frame slot - Bytecode: 0x8c {int8} - Stack: ..., A → ... - replace the Nth (signed) value from the frame pointer in the stack with A @@ -1186,7 +1186,7 @@ Fails unless the last instruction executed was a `callsub`. ## switch -- Syntax: `switch TARGET ...` ∋ TARGET ...: list of labels +- Syntax: `switch TARGET ...` where TARGET ...: list of labels - Bytecode: 0x8d {varuint count, [int16 (big-endian) ...]} - Stack: ..., A: uint64 → ... - branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. @@ -1194,7 +1194,7 @@ Fails unless the last instruction executed was a `callsub`. ## match -- Syntax: `match TARGET ...` ∋ TARGET ...: list of labels +- Syntax: `match TARGET ...` where TARGET ...: list of labels - Bytecode: 0x8e {varuint count, [int16 (big-endian) ...]} - Stack: ..., [A1, A2, ..., AN], B → ... - given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. @@ -1416,7 +1416,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn_field -- Syntax: `itxn_field F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn_field F` where F: [txn](#field-group-txn) - Bytecode: 0xb2 {uint8} - Stack: ..., A → ... - set field F of the current inner transaction to A @@ -1437,7 +1437,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxn -- Syntax: `itxn F` ∋ F: [txn](#field-group-txn) +- Syntax: `itxn F` where F: [txn](#field-group-txn) - Bytecode: 0xb4 {uint8} - Stack: ... → ..., any - field F of the last inner transaction @@ -1446,7 +1446,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## itxna -- Syntax: `itxna F I` ∋ F: [txna](#field-group-txna), I: a transaction field array index +- Syntax: `itxna F I` where F: [txna](#field-group-txna), I: a transaction field array index - Bytecode: 0xb5 {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F of the last inner transaction @@ -1465,7 +1465,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxn -- Syntax: `gitxn T F` ∋ T: transaction group index, F: [txn](#field-group-txn) +- Syntax: `gitxn T F` where T: transaction group index, F: [txn](#field-group-txn) - Bytecode: 0xb7 {uint8}, {uint8} - Stack: ... → ..., any - field F of the Tth transaction in the last inner group submitted @@ -1474,7 +1474,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## gitxna -- Syntax: `gitxna T F I` ∋ T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index +- Syntax: `gitxna T F I` where T: transaction group index, F: [txna](#field-group-txna), I: transaction field array index - Bytecode: 0xb8 {uint8}, {uint8}, {uint8} - Stack: ... → ..., any - Ith value of the array field F from the Tth transaction in the last inner group submitted @@ -1545,7 +1545,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## txnas -- Syntax: `txnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `txnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc0 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the current transaction @@ -1553,7 +1553,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gtxnas -- Syntax: `gtxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gtxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc1 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the current group @@ -1561,7 +1561,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gtxnsas -- Syntax: `gtxnsas F` ∋ F: [txna](#field-group-txna) +- Syntax: `gtxnsas F` where F: [txna](#field-group-txna) - Bytecode: 0xc2 {uint8} - Stack: ..., A: uint64, B: uint64 → ..., any - Bth value of the array field F from the Ath transaction in the current group @@ -1585,7 +1585,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## itxnas -- Syntax: `itxnas F` ∋ F: [txna](#field-group-txna) +- Syntax: `itxnas F` where F: [txna](#field-group-txna) - Bytecode: 0xc5 {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F of the last inner transaction @@ -1594,7 +1594,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## gitxnas -- Syntax: `gitxnas T F` ∋ T: transaction group index, F: [txna](#field-group-txna) +- Syntax: `gitxnas T F` where T: transaction group index, F: [txna](#field-group-txna) - Bytecode: 0xc6 {uint8}, {uint8} - Stack: ..., A: uint64 → ..., any - Ath value of the array field F from the Tth transaction in the last inner group submitted @@ -1603,7 +1603,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo ## vrf_verify -- Syntax: `vrf_verify S` ∋ S: [vrf_verify](#field-group-vrf_verify) +- Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} - Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. @@ -1623,7 +1623,7 @@ Standards ## block -- Syntax: `block F` ∋ F: [block](#field-group-block) +- Syntax: `block F` where F: [block](#field-group-block) - Bytecode: 0xd1 {uint8} - Stack: ..., A: uint64 → ..., any - field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) From ec60b1130b8d31c5f4d49551bf90d4d5092cd693 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:56:33 -0500 Subject: [PATCH 043/218] tests: custom recipe tweaks (#5869) --- .../deployednettemplates/generate-recipe/generate_network.py | 1 + .../deployednettemplates/recipes/custom/configs/node.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/testdata/deployednettemplates/generate-recipe/generate_network.py b/test/testdata/deployednettemplates/generate-recipe/generate_network.py index 0a92aed208..254172aa4d 100755 --- a/test/testdata/deployednettemplates/generate-recipe/generate_network.py +++ b/test/testdata/deployednettemplates/generate-recipe/generate_network.py @@ -70,6 +70,7 @@ def build_net(template_path, netgoal_params): def build_genesis(template_path, netgoal_params, template_dict): args = [ '-t', 'genesis', + '--last-part-key-round', str(100_000), '-o', f"{template_path}/generated/genesis.json" ] args.extend(netgoal_params) diff --git a/test/testdata/deployednettemplates/recipes/custom/configs/node.json b/test/testdata/deployednettemplates/recipes/custom/configs/node.json index 547f38e198..4afe82a04c 100644 --- a/test/testdata/deployednettemplates/recipes/custom/configs/node.json +++ b/test/testdata/deployednettemplates/recipes/custom/configs/node.json @@ -19,7 +19,7 @@ "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"CadaverSizeTarget\": 0 }", - "FractionApply": 0.01 + "FractionApply": 0.1 } ] } From 937c559abd60348fd0d3c534980274288b66398e Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:46:21 -0500 Subject: [PATCH 044/218] Consensus: Upgrade to consensus version v39. (#5868) --- agreement/player_permutation_test.go | 9 +++--- agreement/player_test.go | 32 ++++++++++----------- agreement/service_test.go | 8 +++--- catchup/service_test.go | 4 +-- config/consensus.go | 34 +++++++++++++++-------- data/bookkeeping/block_test.go | 4 +-- data/transactions/logic/assembler_test.go | 27 ++++++++++-------- data/transactions/logic/eval_test.go | 30 +++++++++++++------- data/transactions/logic/langspec_v1.json | 2 +- data/transactions/logic/langspec_v10.json | 2 +- data/transactions/logic/langspec_v2.json | 2 +- data/transactions/logic/langspec_v3.json | 2 +- data/transactions/logic/langspec_v4.json | 2 +- data/transactions/logic/langspec_v5.json | 2 +- data/transactions/logic/langspec_v6.json | 2 +- data/transactions/logic/langspec_v7.json | 2 +- data/transactions/logic/langspec_v8.json | 2 +- data/transactions/logic/langspec_v9.json | 2 +- data/transactions/logic/opcodes.go | 2 +- ledger/testing/consensusRange.go | 1 + ledger/testing/consensusRange_test.go | 2 +- protocol/consensus.go | 8 +++++- 22 files changed, 107 insertions(+), 74 deletions(-) diff --git a/agreement/player_permutation_test.go b/agreement/player_permutation_test.go index bd2c2c84da..2c598ff779 100644 --- a/agreement/player_permutation_test.go +++ b/agreement/player_permutation_test.go @@ -811,17 +811,16 @@ func TestPlayerPermutation(t *testing.T) { } func playerPermutationCheck(t *testing.T, enableDynamicFilterTimeout bool) { - // create a protocol version where dynamic filter is enabled - version, _, configCleanup := createDynamicFilterConfig() + // create a protocol where dynamic filter is set based on the enableDynamicFilterTimeout flag + dynamicFilterOverriddenProtocol, _, configCleanup := overrideConfigWithDynamicFilterParam(enableDynamicFilterTimeout) defer configCleanup() for i := 0; i < 7; i++ { for j := 0; j < 14; j++ { _, pMachine, helper := getPlayerPermutation(t, i) inMsg := getMessageEventPermutation(t, j, helper) - if enableDynamicFilterTimeout { - inMsg.Proto = ConsensusVersionView{Version: version} - } + inMsg.Proto = ConsensusVersionView{Version: dynamicFilterOverriddenProtocol} + err, panicErr := pMachine.transition(inMsg) fmt.Println(pMachine.getTrace().events) fmt.Println("") diff --git a/agreement/player_test.go b/agreement/player_test.go index 2bb2ffe818..a60c9f44c4 100644 --- a/agreement/player_test.go +++ b/agreement/player_test.go @@ -37,7 +37,7 @@ func init() { } func makeTimeoutEvent() timeoutEvent { - return timeoutEvent{T: timeout, RandomEntropy: crypto.RandUint64()} + return timeoutEvent{T: timeout, RandomEntropy: crypto.RandUint64(), Proto: ConsensusVersionView{Version: protocol.ConsensusCurrentVersion}} } func generateProposalEvents(t *testing.T, player player, accs testAccountData, f testBlockFactory, ledger Ledger) (voteBatch []event, payloadBatch []event, lowestProposal proposalValue) { @@ -3240,7 +3240,7 @@ func TestPlayerAlwaysResynchsPinnedValue(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(131) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3262,7 +3262,7 @@ func TestPlayerRetainsReceivedValidatedAtOneSample(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtCredentialHistory(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-credentialRoundLag-1, p, soft) @@ -3301,7 +3301,7 @@ func TestPlayerRetainsReceivedValidatedAtCredentialHistory(t *testing.T) { func TestPlayerRetainsEarlyReceivedValidatedAtOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3339,7 +3339,7 @@ func testClockForRound(t *testing.T, pWhite *player, fixedDur time.Duration, cur func TestPlayerRetainsLateReceivedValidatedAtOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3382,7 +3382,7 @@ func TestPlayerRetainsReceivedValidatedAtForHistoryWindowLateBetter(t *testing.T } func testPlayerRetainsReceivedValidatedAtForHistoryWindow(t *testing.T, addBetterLate bool) { - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3449,7 +3449,7 @@ func testPlayerRetainsReceivedValidatedAtForHistoryWindow(t *testing.T, addBette func TestPlayerRetainsReceivedValidatedAtPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) const p = period(0) @@ -3505,7 +3505,7 @@ func TestPlayerRetainsReceivedValidatedAtPPOneSample(t *testing.T) { func TestPlayerRetainsEarlyReceivedValidatedAtPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) @@ -3559,7 +3559,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtPPOneSample(t *testing.T) { func TestPlayerRetainsLateReceivedValidatedAtPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) const p = period(0) @@ -3613,7 +3613,7 @@ func TestPlayerRetainsLateReceivedValidatedAtPPOneSample(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtPPForHistoryWindow(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3655,7 +3655,7 @@ func TestPlayerRetainsReceivedValidatedAtAVPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) // create a protocol version where dynamic lambda is enabled - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) const p = period(0) @@ -3710,7 +3710,7 @@ func TestPlayerRetainsReceivedValidatedAtAVPPOneSample(t *testing.T) { func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3729,7 +3729,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) { require.Equal(t, pWhite.lowestCredentialArrivals.writePtr, 0) // create a protocol version where dynamic filter is enabled - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() // send votePresent message (mimicking the first AV message validating) @@ -3767,7 +3767,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) { func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3786,7 +3786,7 @@ func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) { require.Equal(t, pWhite.lowestCredentialArrivals.writePtr, 0) // create a protocol version where dynamic filter is enabled - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() // send votePresent message (mimicking the first AV message validating) @@ -3821,7 +3821,7 @@ func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtAVPPHistoryWindow(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) diff --git a/agreement/service_test.go b/agreement/service_test.go index bea5eda8da..0a7ef55f96 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -1050,10 +1050,10 @@ func TestAgreementHistoricalClocksCleanup(t *testing.T) { simulateAgreement(t, 5, int(credentialRoundLag)+10, disabled) } -func createDynamicFilterConfig() (version protocol.ConsensusVersion, consensusVersion func(r basics.Round) (protocol.ConsensusVersion, error), configCleanup func()) { +func overrideConfigWithDynamicFilterParam(dynamicFilterTimeoutEnabled bool) (version protocol.ConsensusVersion, consensusVersion func(r basics.Round) (protocol.ConsensusVersion, error), configCleanup func()) { version = protocol.ConsensusVersion("test-protocol-filtertimeout") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] - protoParams.DynamicFilterTimeout = true + protoParams.DynamicFilterTimeout = dynamicFilterTimeoutEnabled config.Consensus[version] = protoParams consensusVersion = func(r basics.Round) (protocol.ConsensusVersion, error) { @@ -1074,7 +1074,7 @@ func TestAgreementSynchronousFuture5_DynamicFilterRounds(t *testing.T) { t.Skip("Skipping agreement integration test") } - _, consensusVersion, configCleanup := createDynamicFilterConfig() + _, consensusVersion, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() if dynamicFilterCredentialArrivalHistory <= 0 { @@ -1105,7 +1105,7 @@ func TestDynamicFilterTimeoutResets(t *testing.T) { t.Skip("Skipping agreement integration test") } - version, consensusVersion, configCleanup := createDynamicFilterConfig() + version, consensusVersion, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() if dynamicFilterCredentialArrivalHistory <= 0 { diff --git a/catchup/service_test.go b/catchup/service_test.go index 0c4cb5cc6b..c712fab5a3 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -1135,7 +1135,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { lookback := lookbackForStateproofsSupport(&topBlk) oldestRound := topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) - assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusFuture].StateProofInterval-config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback) + assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval-config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback) // the network has made progress and now it is on round 8000. in this case we would not download blocks to cover 512. // instead, we will download blocks to confirm only the recovery period lookback. @@ -1150,7 +1150,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { oldestRound = topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) lowestRoundToRetain := 8000 - (8000 % config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval) - - config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*(config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals+1) - config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*(config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals+1) - config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback assert.Equal(t, uint64(oldestRound), lowestRoundToRetain) diff --git a/config/consensus.go b/config/consensus.go index 992cc90ce9..a1baa92044 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1383,22 +1383,34 @@ func initConsensusProtocols() { // for the sake of future manual calculations, we'll round that down a bit : v37.ApprovedUpgrades[protocol.ConsensusV38] = 10000 - // ConsensusFuture is used to test features that are implemented - // but not yet released in a production protocol version. - vFuture := v38 + v39 := v38 + v39.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + v39.LogicSigVersion = 10 + v39.EnableLogicSigCostPooling = true + + v39.AgreementDeadlineTimeoutPeriod0 = 4 * time.Second + + v39.DynamicFilterTimeout = true - vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here - vFuture.EnableLogicSigCostPooling = true + v39.StateProofBlockHashInLightHeader = true - vFuture.AgreementDeadlineTimeoutPeriod0 = 4 * time.Second + // For future upgrades, round times will likely be shorter so giving ourselves some buffer room + v39.MaxUpgradeWaitRounds = 250000 - vFuture.StateProofBlockHashInLightHeader = true + Consensus[protocol.ConsensusV39] = v39 + + // v38 can be upgraded to v39, with an update delay of 7d: + // 157000 = (7 * 24 * 60 * 60 / 3.3 round times currently) + // but our current max is 150000 so using that : + v38.ApprovedUpgrades[protocol.ConsensusV39] = 150000 + + // ConsensusFuture is used to test features that are implemented + // but not yet released in a production protocol version. + vFuture := v39 + vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // Setting DynamicFilterTimeout in vFuture will impact e2e test performance - // by reducing round time. Hence, it is commented out for now. - vFuture.DynamicFilterTimeout = true + vFuture.LogicSigVersion = 11 // When moving this to a release, put a new higher LogicSigVersion here Consensus[protocol.ConsensusFuture] = vFuture diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go index 2b4fa8c81f..a4cc3d6382 100644 --- a/data/bookkeeping/block_test.go +++ b/data/bookkeeping/block_test.go @@ -832,9 +832,9 @@ func TestBlock_ContentsMatchHeader(t *testing.T) { copy(block.BlockHeader.TxnCommitments.Sha256Commitment[:], rootSliceSHA256) a.False(block.ContentsMatchHeader()) - /* Test Consensus Future */ + /* Test Consensus Current */ // Create a block with SHA256 TxnCommitments - block.CurrentProtocol = protocol.ConsensusFuture + block.CurrentProtocol = protocol.ConsensusCurrentVersion block.BlockHeader.TxnCommitments.NativeSha512_256Commitment = crypto.Digest{} block.BlockHeader.TxnCommitments.Sha256Commitment = crypto.Digest{} diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 0963ee1486..c45c0af77d 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -438,6 +438,8 @@ const spliceNonsence = ` const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence +const v11Nonsense = v10Nonsense + const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" const randomnessCompiled = "81ffff03d101d000" @@ -458,6 +460,8 @@ const spliceCompiled = "d2d3" const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled +const V11Compiled = v10Compiled + var nonsense = map[uint64]string{ 1: v1Nonsense, 2: v2Nonsense, @@ -469,20 +473,21 @@ var nonsense = map[uint64]string{ 8: v8Nonsense, 9: v9Nonsense, 10: v10Nonsense, + 11: v11Nonsense, } var compiled = map[uint64]string{ - 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494", - 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", - 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", - 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", - 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03", - 6: "06" + v6Compiled, - 7: "07" + v7Compiled, - 8: "08" + v8Compiled, - 9: "09" + v9Compiled, - + 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494", + 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", + 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", + 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", + 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03", + 6: "06" + v6Compiled, + 7: "07" + v7Compiled, + 8: "08" + v8Compiled, + 9: "09" + v9Compiled, 10: "0a" + v10Compiled, + 11: "0b" + V11Compiled, } func pseudoOp(opcode string) bool { @@ -536,7 +541,7 @@ func TestAssemble(t *testing.T) { } } -var experiments = []uint64{pairingVersion, spliceVersion} +var experiments = []uint64{} // TestExperimental forces a conscious choice to promote "experimental" opcode // groups. This will fail when we increment vFuture's LogicSigVersion. If we had diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 481f9544fe..05e5786013 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1235,6 +1235,10 @@ global AssetOptInMinBalance; int 1001; ==; && global GenesisHash; len; int 32; ==; && ` +const globalV11TestProgram = globalV10TestProgram + ` +// No new globals in v11 +` + func TestGlobal(t *testing.T) { partitiontest.PartitionTest(t) @@ -1256,6 +1260,7 @@ func TestGlobal(t *testing.T) { 8: {CallerApplicationAddress, globalV8TestProgram}, 9: {CallerApplicationAddress, globalV9TestProgram}, 10: {GenesisHash, globalV10TestProgram}, + 11: {GenesisHash, globalV11TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) @@ -1767,6 +1772,11 @@ assert int 1 ` +const testTxnProgramTextV11 = testTxnProgramTextV10 + ` +assert +int 1 +` + func makeSampleTxn() transactions.SignedTxn { var txn transactions.SignedTxn copy(txn.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) @@ -1870,17 +1880,17 @@ func TestTxn(t *testing.T) { t.Parallel() tests := map[uint64]string{ - 1: testTxnProgramTextV1, - 2: testTxnProgramTextV2, - 3: testTxnProgramTextV3, - 4: testTxnProgramTextV4, - 5: testTxnProgramTextV5, - 6: testTxnProgramTextV6, - 7: testTxnProgramTextV7, - 8: testTxnProgramTextV8, - 9: testTxnProgramTextV9, - + 1: testTxnProgramTextV1, + 2: testTxnProgramTextV2, + 3: testTxnProgramTextV3, + 4: testTxnProgramTextV4, + 5: testTxnProgramTextV5, + 6: testTxnProgramTextV6, + 7: testTxnProgramTextV7, + 8: testTxnProgramTextV8, + 9: testTxnProgramTextV9, 10: testTxnProgramTextV10, + 11: testTxnProgramTextV11, } for i, txnField := range TxnFieldNames { diff --git a/data/transactions/logic/langspec_v1.json b/data/transactions/logic/langspec_v1.json index b659e662af..6839e40926 100644 --- a/data/transactions/logic/langspec_v1.json +++ b/data/transactions/logic/langspec_v1.json @@ -1,6 +1,6 @@ { "Version": 1, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json index 2141e8fe0f..d971b27154 100644 --- a/data/transactions/logic/langspec_v10.json +++ b/data/transactions/logic/langspec_v10.json @@ -1,6 +1,6 @@ { "Version": 10, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v2.json b/data/transactions/logic/langspec_v2.json index b518b1a42f..a832f86643 100644 --- a/data/transactions/logic/langspec_v2.json +++ b/data/transactions/logic/langspec_v2.json @@ -1,6 +1,6 @@ { "Version": 2, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v3.json b/data/transactions/logic/langspec_v3.json index a8bc6df0da..f1566b5289 100644 --- a/data/transactions/logic/langspec_v3.json +++ b/data/transactions/logic/langspec_v3.json @@ -1,6 +1,6 @@ { "Version": 3, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v4.json b/data/transactions/logic/langspec_v4.json index ffc428822b..49591c10f6 100644 --- a/data/transactions/logic/langspec_v4.json +++ b/data/transactions/logic/langspec_v4.json @@ -1,6 +1,6 @@ { "Version": 4, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v5.json b/data/transactions/logic/langspec_v5.json index 2b946629cc..ccde5509fc 100644 --- a/data/transactions/logic/langspec_v5.json +++ b/data/transactions/logic/langspec_v5.json @@ -1,6 +1,6 @@ { "Version": 5, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v6.json b/data/transactions/logic/langspec_v6.json index 20575dae5a..72e1d9c64f 100644 --- a/data/transactions/logic/langspec_v6.json +++ b/data/transactions/logic/langspec_v6.json @@ -1,6 +1,6 @@ { "Version": 6, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v7.json b/data/transactions/logic/langspec_v7.json index d8be33960c..7229ee534c 100644 --- a/data/transactions/logic/langspec_v7.json +++ b/data/transactions/logic/langspec_v7.json @@ -1,6 +1,6 @@ { "Version": 7, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json index 4963f4c85a..2f1576864a 100644 --- a/data/transactions/logic/langspec_v8.json +++ b/data/transactions/logic/langspec_v8.json @@ -1,6 +1,6 @@ { "Version": 8, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json index 50418be824..3ec2d39af7 100644 --- a/data/transactions/logic/langspec_v9.json +++ b/data/transactions/logic/langspec_v9.json @@ -1,6 +1,6 @@ { "Version": 9, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index fa3fd22625..1f595dfb7c 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -27,7 +27,7 @@ import ( ) // LogicVersion defines default assembler and max eval versions -const LogicVersion = 10 +const LogicVersion = 11 // rekeyingEnabledVersion is the version of TEAL where RekeyTo functionality // was enabled. This is important to remember so that old TEAL accounts cannot diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 019b502717..97693bbe2b 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -60,6 +60,7 @@ var consensusByNumber = []protocol.ConsensusVersion{ protocol.ConsensusV36, // AVM v8, box storage protocol.ConsensusV37, protocol.ConsensusV38, // AVM v9, ECDSA pre-check, stateproofs recoverability + protocol.ConsensusV39, // AVM v10, logicsig opcode budget pooling, elliptic curve ops, dynamic round times protocol.ConsensusFuture, } diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go index 26e0420946..f1a9c179e9 100644 --- a/ledger/testing/consensusRange_test.go +++ b/ledger/testing/consensusRange_test.go @@ -56,6 +56,6 @@ func TestReleasedVersion(t *testing.T) { } require.Equal(t, versionStringFromIndex(len(consensusByNumber)-1), "vFuture") - require.Equal(t, versionStringFromIndex(38), "v38") + require.Equal(t, versionStringFromIndex(39), "v39") } diff --git a/protocol/consensus.go b/protocol/consensus.go index b32e245662..928ba4b6f0 100644 --- a/protocol/consensus.go +++ b/protocol/consensus.go @@ -217,6 +217,12 @@ const ConsensusV38 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/abd3d4823c6f77349fc04c3af7b1e99fe4df699f", ) +// ConsensusV39 enables dynamic filter timeouts, a deadline timeout of 4 seconds, +// TEAL v10 logicSig opcode budget pooling along with elliptic curve ops on some pairing friendly curves. +const ConsensusV39 = ConsensusVersion( + "https://github.com/algorandfoundation/specs/tree/925a46433742afb0b51bb939354bd907fa88bf95", +) + // ConsensusFuture is a protocol that should not appear in any production // network, but is used to test features before they are released. const ConsensusFuture = ConsensusVersion( @@ -246,7 +252,7 @@ const ConsensusVAlpha5 = ConsensusVersion("alpha5") // ConsensusCurrentVersion is the latest version and should be used // when a specific version is not provided. -const ConsensusCurrentVersion = ConsensusV38 +const ConsensusCurrentVersion = ConsensusV39 // Error is used to indicate that an unsupported protocol has been detected. type Error ConsensusVersion From 1cfb80a852bf8bb9c76921b88fe8ef12a391b047 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 14 Dec 2023 14:46:21 -0500 Subject: [PATCH 045/218] Consensus: Upgrade to consensus version v39. (#5868) --- agreement/player_permutation_test.go | 9 +++--- agreement/player_test.go | 32 ++++++++++----------- agreement/service_test.go | 8 +++--- catchup/service_test.go | 4 +-- config/consensus.go | 34 +++++++++++++++-------- data/bookkeeping/block_test.go | 4 +-- data/transactions/logic/assembler_test.go | 27 ++++++++++-------- data/transactions/logic/eval_test.go | 32 +++++++++++++-------- data/transactions/logic/langspec_v1.json | 2 +- data/transactions/logic/langspec_v10.json | 2 +- data/transactions/logic/langspec_v2.json | 2 +- data/transactions/logic/langspec_v3.json | 2 +- data/transactions/logic/langspec_v4.json | 2 +- data/transactions/logic/langspec_v5.json | 2 +- data/transactions/logic/langspec_v6.json | 2 +- data/transactions/logic/langspec_v7.json | 2 +- data/transactions/logic/langspec_v8.json | 2 +- data/transactions/logic/langspec_v9.json | 2 +- data/transactions/logic/opcodes.go | 2 +- ledger/testing/consensusRange.go | 1 + ledger/testing/consensusRange_test.go | 2 +- protocol/consensus.go | 8 +++++- 22 files changed, 108 insertions(+), 75 deletions(-) diff --git a/agreement/player_permutation_test.go b/agreement/player_permutation_test.go index bd2c2c84da..2c598ff779 100644 --- a/agreement/player_permutation_test.go +++ b/agreement/player_permutation_test.go @@ -811,17 +811,16 @@ func TestPlayerPermutation(t *testing.T) { } func playerPermutationCheck(t *testing.T, enableDynamicFilterTimeout bool) { - // create a protocol version where dynamic filter is enabled - version, _, configCleanup := createDynamicFilterConfig() + // create a protocol where dynamic filter is set based on the enableDynamicFilterTimeout flag + dynamicFilterOverriddenProtocol, _, configCleanup := overrideConfigWithDynamicFilterParam(enableDynamicFilterTimeout) defer configCleanup() for i := 0; i < 7; i++ { for j := 0; j < 14; j++ { _, pMachine, helper := getPlayerPermutation(t, i) inMsg := getMessageEventPermutation(t, j, helper) - if enableDynamicFilterTimeout { - inMsg.Proto = ConsensusVersionView{Version: version} - } + inMsg.Proto = ConsensusVersionView{Version: dynamicFilterOverriddenProtocol} + err, panicErr := pMachine.transition(inMsg) fmt.Println(pMachine.getTrace().events) fmt.Println("") diff --git a/agreement/player_test.go b/agreement/player_test.go index 2bb2ffe818..a60c9f44c4 100644 --- a/agreement/player_test.go +++ b/agreement/player_test.go @@ -37,7 +37,7 @@ func init() { } func makeTimeoutEvent() timeoutEvent { - return timeoutEvent{T: timeout, RandomEntropy: crypto.RandUint64()} + return timeoutEvent{T: timeout, RandomEntropy: crypto.RandUint64(), Proto: ConsensusVersionView{Version: protocol.ConsensusCurrentVersion}} } func generateProposalEvents(t *testing.T, player player, accs testAccountData, f testBlockFactory, ledger Ledger) (voteBatch []event, payloadBatch []event, lowestProposal proposalValue) { @@ -3240,7 +3240,7 @@ func TestPlayerAlwaysResynchsPinnedValue(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(131) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3262,7 +3262,7 @@ func TestPlayerRetainsReceivedValidatedAtOneSample(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtCredentialHistory(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-credentialRoundLag-1, p, soft) @@ -3301,7 +3301,7 @@ func TestPlayerRetainsReceivedValidatedAtCredentialHistory(t *testing.T) { func TestPlayerRetainsEarlyReceivedValidatedAtOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3339,7 +3339,7 @@ func testClockForRound(t *testing.T, pWhite *player, fixedDur time.Duration, cur func TestPlayerRetainsLateReceivedValidatedAtOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3382,7 +3382,7 @@ func TestPlayerRetainsReceivedValidatedAtForHistoryWindowLateBetter(t *testing.T } func testPlayerRetainsReceivedValidatedAtForHistoryWindow(t *testing.T, addBetterLate bool) { - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3449,7 +3449,7 @@ func testPlayerRetainsReceivedValidatedAtForHistoryWindow(t *testing.T, addBette func TestPlayerRetainsReceivedValidatedAtPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) const p = period(0) @@ -3505,7 +3505,7 @@ func TestPlayerRetainsReceivedValidatedAtPPOneSample(t *testing.T) { func TestPlayerRetainsEarlyReceivedValidatedAtPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) @@ -3559,7 +3559,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtPPOneSample(t *testing.T) { func TestPlayerRetainsLateReceivedValidatedAtPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) const p = period(0) @@ -3613,7 +3613,7 @@ func TestPlayerRetainsLateReceivedValidatedAtPPOneSample(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtPPForHistoryWindow(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3655,7 +3655,7 @@ func TestPlayerRetainsReceivedValidatedAtAVPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) // create a protocol version where dynamic lambda is enabled - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() const r = round(20239) const p = period(0) @@ -3710,7 +3710,7 @@ func TestPlayerRetainsReceivedValidatedAtAVPPOneSample(t *testing.T) { func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3729,7 +3729,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) { require.Equal(t, pWhite.lowestCredentialArrivals.writePtr, 0) // create a protocol version where dynamic filter is enabled - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() // send votePresent message (mimicking the first AV message validating) @@ -3767,7 +3767,7 @@ func TestPlayerRetainsEarlyReceivedValidatedAtAVPPOneSample(t *testing.T) { func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) @@ -3786,7 +3786,7 @@ func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) { require.Equal(t, pWhite.lowestCredentialArrivals.writePtr, 0) // create a protocol version where dynamic filter is enabled - version, _, configCleanup := createDynamicFilterConfig() + version, _, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() // send votePresent message (mimicking the first AV message validating) @@ -3821,7 +3821,7 @@ func TestPlayerRetainsLateReceivedValidatedAtAVPPOneSample(t *testing.T) { func TestPlayerRetainsReceivedValidatedAtAVPPHistoryWindow(t *testing.T) { partitiontest.PartitionTest(t) - version := protocol.ConsensusFuture + version := protocol.ConsensusCurrentVersion const r = round(20239) const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) diff --git a/agreement/service_test.go b/agreement/service_test.go index bea5eda8da..0a7ef55f96 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -1050,10 +1050,10 @@ func TestAgreementHistoricalClocksCleanup(t *testing.T) { simulateAgreement(t, 5, int(credentialRoundLag)+10, disabled) } -func createDynamicFilterConfig() (version protocol.ConsensusVersion, consensusVersion func(r basics.Round) (protocol.ConsensusVersion, error), configCleanup func()) { +func overrideConfigWithDynamicFilterParam(dynamicFilterTimeoutEnabled bool) (version protocol.ConsensusVersion, consensusVersion func(r basics.Round) (protocol.ConsensusVersion, error), configCleanup func()) { version = protocol.ConsensusVersion("test-protocol-filtertimeout") protoParams := config.Consensus[protocol.ConsensusCurrentVersion] - protoParams.DynamicFilterTimeout = true + protoParams.DynamicFilterTimeout = dynamicFilterTimeoutEnabled config.Consensus[version] = protoParams consensusVersion = func(r basics.Round) (protocol.ConsensusVersion, error) { @@ -1074,7 +1074,7 @@ func TestAgreementSynchronousFuture5_DynamicFilterRounds(t *testing.T) { t.Skip("Skipping agreement integration test") } - _, consensusVersion, configCleanup := createDynamicFilterConfig() + _, consensusVersion, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() if dynamicFilterCredentialArrivalHistory <= 0 { @@ -1105,7 +1105,7 @@ func TestDynamicFilterTimeoutResets(t *testing.T) { t.Skip("Skipping agreement integration test") } - version, consensusVersion, configCleanup := createDynamicFilterConfig() + version, consensusVersion, configCleanup := overrideConfigWithDynamicFilterParam(true) defer configCleanup() if dynamicFilterCredentialArrivalHistory <= 0 { diff --git a/catchup/service_test.go b/catchup/service_test.go index 0c4cb5cc6b..c712fab5a3 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -1135,7 +1135,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { lookback := lookbackForStateproofsSupport(&topBlk) oldestRound := topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) - assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusFuture].StateProofInterval-config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback) + assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval-config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback) // the network has made progress and now it is on round 8000. in this case we would not download blocks to cover 512. // instead, we will download blocks to confirm only the recovery period lookback. @@ -1150,7 +1150,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { oldestRound = topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) lowestRoundToRetain := 8000 - (8000 % config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval) - - config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*(config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals+1) - config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*(config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals+1) - config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback assert.Equal(t, uint64(oldestRound), lowestRoundToRetain) diff --git a/config/consensus.go b/config/consensus.go index 992cc90ce9..a1baa92044 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1383,22 +1383,34 @@ func initConsensusProtocols() { // for the sake of future manual calculations, we'll round that down a bit : v37.ApprovedUpgrades[protocol.ConsensusV38] = 10000 - // ConsensusFuture is used to test features that are implemented - // but not yet released in a production protocol version. - vFuture := v38 + v39 := v38 + v39.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + v39.LogicSigVersion = 10 + v39.EnableLogicSigCostPooling = true + + v39.AgreementDeadlineTimeoutPeriod0 = 4 * time.Second + + v39.DynamicFilterTimeout = true - vFuture.LogicSigVersion = 10 // When moving this to a release, put a new higher LogicSigVersion here - vFuture.EnableLogicSigCostPooling = true + v39.StateProofBlockHashInLightHeader = true - vFuture.AgreementDeadlineTimeoutPeriod0 = 4 * time.Second + // For future upgrades, round times will likely be shorter so giving ourselves some buffer room + v39.MaxUpgradeWaitRounds = 250000 - vFuture.StateProofBlockHashInLightHeader = true + Consensus[protocol.ConsensusV39] = v39 + + // v38 can be upgraded to v39, with an update delay of 7d: + // 157000 = (7 * 24 * 60 * 60 / 3.3 round times currently) + // but our current max is 150000 so using that : + v38.ApprovedUpgrades[protocol.ConsensusV39] = 150000 + + // ConsensusFuture is used to test features that are implemented + // but not yet released in a production protocol version. + vFuture := v39 + vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // Setting DynamicFilterTimeout in vFuture will impact e2e test performance - // by reducing round time. Hence, it is commented out for now. - vFuture.DynamicFilterTimeout = true + vFuture.LogicSigVersion = 11 // When moving this to a release, put a new higher LogicSigVersion here Consensus[protocol.ConsensusFuture] = vFuture diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go index 2b4fa8c81f..a4cc3d6382 100644 --- a/data/bookkeeping/block_test.go +++ b/data/bookkeeping/block_test.go @@ -832,9 +832,9 @@ func TestBlock_ContentsMatchHeader(t *testing.T) { copy(block.BlockHeader.TxnCommitments.Sha256Commitment[:], rootSliceSHA256) a.False(block.ContentsMatchHeader()) - /* Test Consensus Future */ + /* Test Consensus Current */ // Create a block with SHA256 TxnCommitments - block.CurrentProtocol = protocol.ConsensusFuture + block.CurrentProtocol = protocol.ConsensusCurrentVersion block.BlockHeader.TxnCommitments.NativeSha512_256Commitment = crypto.Digest{} block.BlockHeader.TxnCommitments.Sha256Commitment = crypto.Digest{} diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 0963ee1486..c45c0af77d 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -438,6 +438,8 @@ const spliceNonsence = ` const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence +const v11Nonsense = v10Nonsense + const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" const randomnessCompiled = "81ffff03d101d000" @@ -458,6 +460,8 @@ const spliceCompiled = "d2d3" const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled +const V11Compiled = v10Compiled + var nonsense = map[uint64]string{ 1: v1Nonsense, 2: v2Nonsense, @@ -469,20 +473,21 @@ var nonsense = map[uint64]string{ 8: v8Nonsense, 9: v9Nonsense, 10: v10Nonsense, + 11: v11Nonsense, } var compiled = map[uint64]string{ - 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494", - 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", - 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", - 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", - 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03", - 6: "06" + v6Compiled, - 7: "07" + v7Compiled, - 8: "08" + v8Compiled, - 9: "09" + v9Compiled, - + 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b1716154000032903494", + 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", + 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f2310231123122313231418191a1b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", + 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", + 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03", + 6: "06" + v6Compiled, + 7: "07" + v7Compiled, + 8: "08" + v8Compiled, + 9: "09" + v9Compiled, 10: "0a" + v10Compiled, + 11: "0b" + V11Compiled, } func pseudoOp(opcode string) bool { @@ -536,7 +541,7 @@ func TestAssemble(t *testing.T) { } } -var experiments = []uint64{pairingVersion, spliceVersion} +var experiments = []uint64{} // TestExperimental forces a conscious choice to promote "experimental" opcode // groups. This will fail when we increment vFuture's LogicSigVersion. If we had diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index fd8b9ff806..2b85adbac1 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1234,6 +1234,10 @@ global AssetCreateMinBalance; int 1001; ==; && global AssetOptInMinBalance; int 1001; ==; && ` +const globalV11TestProgram = globalV10TestProgram + ` +// No new globals in v11 +` + func TestGlobal(t *testing.T) { partitiontest.PartitionTest(t) @@ -1254,7 +1258,8 @@ func TestGlobal(t *testing.T) { 7: {CallerApplicationAddress, globalV7TestProgram}, 8: {CallerApplicationAddress, globalV8TestProgram}, 9: {CallerApplicationAddress, globalV9TestProgram}, - 10: {AssetOptInMinBalance, globalV10TestProgram}, + 10: {GenesisHash, globalV10TestProgram}, + 11: {GenesisHash, globalV11TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) @@ -1762,6 +1767,11 @@ assert int 1 ` +const testTxnProgramTextV11 = testTxnProgramTextV10 + ` +assert +int 1 +` + func makeSampleTxn() transactions.SignedTxn { var txn transactions.SignedTxn copy(txn.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) @@ -1865,17 +1875,17 @@ func TestTxn(t *testing.T) { t.Parallel() tests := map[uint64]string{ - 1: testTxnProgramTextV1, - 2: testTxnProgramTextV2, - 3: testTxnProgramTextV3, - 4: testTxnProgramTextV4, - 5: testTxnProgramTextV5, - 6: testTxnProgramTextV6, - 7: testTxnProgramTextV7, - 8: testTxnProgramTextV8, - 9: testTxnProgramTextV9, - + 1: testTxnProgramTextV1, + 2: testTxnProgramTextV2, + 3: testTxnProgramTextV3, + 4: testTxnProgramTextV4, + 5: testTxnProgramTextV5, + 6: testTxnProgramTextV6, + 7: testTxnProgramTextV7, + 8: testTxnProgramTextV8, + 9: testTxnProgramTextV9, 10: testTxnProgramTextV10, + 11: testTxnProgramTextV11, } for i, txnField := range TxnFieldNames { diff --git a/data/transactions/logic/langspec_v1.json b/data/transactions/logic/langspec_v1.json index b659e662af..6839e40926 100644 --- a/data/transactions/logic/langspec_v1.json +++ b/data/transactions/logic/langspec_v1.json @@ -1,6 +1,6 @@ { "Version": 1, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json index 2141e8fe0f..d971b27154 100644 --- a/data/transactions/logic/langspec_v10.json +++ b/data/transactions/logic/langspec_v10.json @@ -1,6 +1,6 @@ { "Version": 10, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v2.json b/data/transactions/logic/langspec_v2.json index b518b1a42f..a832f86643 100644 --- a/data/transactions/logic/langspec_v2.json +++ b/data/transactions/logic/langspec_v2.json @@ -1,6 +1,6 @@ { "Version": 2, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v3.json b/data/transactions/logic/langspec_v3.json index a8bc6df0da..f1566b5289 100644 --- a/data/transactions/logic/langspec_v3.json +++ b/data/transactions/logic/langspec_v3.json @@ -1,6 +1,6 @@ { "Version": 3, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v4.json b/data/transactions/logic/langspec_v4.json index ffc428822b..49591c10f6 100644 --- a/data/transactions/logic/langspec_v4.json +++ b/data/transactions/logic/langspec_v4.json @@ -1,6 +1,6 @@ { "Version": 4, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v5.json b/data/transactions/logic/langspec_v5.json index 2b946629cc..ccde5509fc 100644 --- a/data/transactions/logic/langspec_v5.json +++ b/data/transactions/logic/langspec_v5.json @@ -1,6 +1,6 @@ { "Version": 5, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v6.json b/data/transactions/logic/langspec_v6.json index 20575dae5a..72e1d9c64f 100644 --- a/data/transactions/logic/langspec_v6.json +++ b/data/transactions/logic/langspec_v6.json @@ -1,6 +1,6 @@ { "Version": 6, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v7.json b/data/transactions/logic/langspec_v7.json index d8be33960c..7229ee534c 100644 --- a/data/transactions/logic/langspec_v7.json +++ b/data/transactions/logic/langspec_v7.json @@ -1,6 +1,6 @@ { "Version": 7, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json index 4963f4c85a..2f1576864a 100644 --- a/data/transactions/logic/langspec_v8.json +++ b/data/transactions/logic/langspec_v8.json @@ -1,6 +1,6 @@ { "Version": 8, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json index 50418be824..3ec2d39af7 100644 --- a/data/transactions/logic/langspec_v9.json +++ b/data/transactions/logic/langspec_v9.json @@ -1,6 +1,6 @@ { "Version": 9, - "LogicSigVersion": 9, + "LogicSigVersion": 10, "NamedTypes": [ { "Name": "[32]byte", diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index fa3fd22625..1f595dfb7c 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -27,7 +27,7 @@ import ( ) // LogicVersion defines default assembler and max eval versions -const LogicVersion = 10 +const LogicVersion = 11 // rekeyingEnabledVersion is the version of TEAL where RekeyTo functionality // was enabled. This is important to remember so that old TEAL accounts cannot diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 019b502717..97693bbe2b 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -60,6 +60,7 @@ var consensusByNumber = []protocol.ConsensusVersion{ protocol.ConsensusV36, // AVM v8, box storage protocol.ConsensusV37, protocol.ConsensusV38, // AVM v9, ECDSA pre-check, stateproofs recoverability + protocol.ConsensusV39, // AVM v10, logicsig opcode budget pooling, elliptic curve ops, dynamic round times protocol.ConsensusFuture, } diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go index 26e0420946..f1a9c179e9 100644 --- a/ledger/testing/consensusRange_test.go +++ b/ledger/testing/consensusRange_test.go @@ -56,6 +56,6 @@ func TestReleasedVersion(t *testing.T) { } require.Equal(t, versionStringFromIndex(len(consensusByNumber)-1), "vFuture") - require.Equal(t, versionStringFromIndex(38), "v38") + require.Equal(t, versionStringFromIndex(39), "v39") } diff --git a/protocol/consensus.go b/protocol/consensus.go index b32e245662..928ba4b6f0 100644 --- a/protocol/consensus.go +++ b/protocol/consensus.go @@ -217,6 +217,12 @@ const ConsensusV38 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/abd3d4823c6f77349fc04c3af7b1e99fe4df699f", ) +// ConsensusV39 enables dynamic filter timeouts, a deadline timeout of 4 seconds, +// TEAL v10 logicSig opcode budget pooling along with elliptic curve ops on some pairing friendly curves. +const ConsensusV39 = ConsensusVersion( + "https://github.com/algorandfoundation/specs/tree/925a46433742afb0b51bb939354bd907fa88bf95", +) + // ConsensusFuture is a protocol that should not appear in any production // network, but is used to test features before they are released. const ConsensusFuture = ConsensusVersion( @@ -246,7 +252,7 @@ const ConsensusVAlpha5 = ConsensusVersion("alpha5") // ConsensusCurrentVersion is the latest version and should be used // when a specific version is not provided. -const ConsensusCurrentVersion = ConsensusV38 +const ConsensusCurrentVersion = ConsensusV39 // Error is used to indicate that an unsupported protocol has been detected. type Error ConsensusVersion From 8f8ada5dfd10d82def3a9e171ce21a49fb86dd42 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:34:32 -0500 Subject: [PATCH 046/218] txHandler: fix ARL triggering without ERL (#5872) --- data/txHandler.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/txHandler.go b/data/txHandler.go index 4689a497b4..9e6e1094c4 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -597,8 +597,10 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net var err error var capguard *util.ErlCapacityGuard var congested bool - if handler.erl != nil { + if handler.erl != nil || handler.appLimiter != nil { congested = float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue)) + } + if handler.erl != nil { // consume a capacity unit // if the elastic rate limiter cannot vend a capacity, the error it returns // is sufficient to indicate that we should enable Congestion Control, because From 49f95cd61c2bab672b1bd45ee909a52f1320d2f1 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:42:49 -0500 Subject: [PATCH 047/218] network: fix TestSlowPeerDisconnection regression after #5634 (#5874) --- network/wsNetwork_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index f8eeeb71f4..7bc3b64f1e 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -2567,8 +2567,8 @@ func TestSlowPeerDisconnection(t *testing.T) { GenesisID: genesisID, NetworkID: config.Devtestnet, } - wn.broadcaster.slowWritingPeerMonitorInterval = time.Millisecond * 50 wn.setup() + wn.broadcaster.slowWritingPeerMonitorInterval = time.Millisecond * 50 wn.eventualReadyDelay = time.Second wn.messagesOfInterest = nil // clear this before starting the network so that we won't be sending a MOI upon connection. From 2b914fc68178a1e1f06bead42cbc88c24630b5ed Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 19 Dec 2023 10:07:34 -0500 Subject: [PATCH 048/218] CICD: fix aptly repo management (#5870) --- .aptly.conf | 2 -- docker/build/aptly.Dockerfile | 24 +++++------------ docker/build/docker.ubuntu.Dockerfile | 14 ++++------ package-deploy.yaml | 5 ++-- scripts/release/mule/deploy/deb/deploy.sh | 32 ++--------------------- 5 files changed, 16 insertions(+), 61 deletions(-) diff --git a/.aptly.conf b/.aptly.conf index 6300abc3b7..8badff78da 100644 --- a/.aptly.conf +++ b/.aptly.conf @@ -1,5 +1,4 @@ { - "rootDir": "/root/aptly", "downloadConcurrency": 4, "downloadSpeedLimit": 0, "architectures": [], @@ -27,4 +26,3 @@ }, "SwiftPublishEndpoints": {} } - diff --git a/docker/build/aptly.Dockerfile b/docker/build/aptly.Dockerfile index 1849d8e5e1..24982e7412 100644 --- a/docker/build/aptly.Dockerfile +++ b/docker/build/aptly.Dockerfile @@ -1,26 +1,16 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG ARCH=amd64 -ARG GOLANG_VERSION -ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update && apt-get install aptly awscli binutils build-essential curl gnupg2 -y -RUN curl https://dl.google.com/go/go${GOLANG_VERSION}.linux-${ARCH%v*}.tar.gz | tar -xzf - && mv go /usr/local -ENV GOROOT=/usr/local/go \ - GOPATH=/root/go \ - PATH=$GOPATH/bin:$GOROOT/bin:$PATH +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install aptly awscli binutils build-essential curl gnupg2 -y WORKDIR /root COPY .aptly.conf . -RUN curl https://releases.algorand.com/key.pub | gpg --no-default-keyring --keyring trustedkeys.gpg --import - && \ - aptly mirror create stable https://releases.algorand.com/deb/ stable main && \ +RUN curl https://releases.algorand.com/key.pub | gpg --no-default-keyring --keyring /root/.gnupg/trustedkeys.gpg --import - +RUN gpg --no-default-keyring --keyring /root/.gnupg/trustedkeys.gpg --export --output /root/.gnupg/newkeyring.gpg && mv -f /root/.gnupg/newkeyring.gpg /root/.gnupg/trustedkeys.gpg +RUN aptly mirror create stable https://releases.algorand.com/deb/ stable main && \ aptly mirror create beta https://releases.algorand.com/deb/ beta main && \ - aptly repo create -distribution=stable -architectures=amd64 -component=main -comment=mainnet stable && \ - aptly repo create -distribution=beta -architectures=amd64 -component=main -comment=betanet beta && \ - aptly mirror update stable && \ - aptly mirror update beta && \ - aptly repo import stable stable algorand algorand-devtools && \ - aptly repo import beta beta algorand-beta algorand-devtools-beta + aptly repo create -distribution=stable -architectures=amd64,arm64 -component=main -comment=mainnet stable && \ + aptly repo create -distribution=beta -architectures=amd64,arm64 -component=main -comment=betanet beta CMD ["/bin/bash"] - diff --git a/docker/build/docker.ubuntu.Dockerfile b/docker/build/docker.ubuntu.Dockerfile index 5091afefa9..e82dba3d71 100644 --- a/docker/build/docker.ubuntu.Dockerfile +++ b/docker/build/docker.ubuntu.Dockerfile @@ -3,18 +3,14 @@ ARG ARCH="amd64" FROM ${ARCH}/ubuntu:20.04 ARG GOLANG_VERSION ARG ARCH="amd64" -RUN apt-get update && apt-get install curl python python3.7 python3-pip build-essential apt-transport-https ca-certificates software-properties-common -y && \ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install curl python python3.7 python3-pip build-essential apt-transport-https ca-certificates software-properties-common -y && \ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \ - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \ - apt-get update && apt-get install docker-ce -y + DEBIAN_FRONTEND=noninteractive add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \ + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install docker-ce -y -# Mule needs >= python3.7 so set that as the default. -RUN update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3.7 2 && \ - update-alternatives --set python /usr/bin/python3.7 && \ - pip3 install mulecli +RUN pip3 install mulecli -RUN apt-get update && apt-get install -y autoconf bsdmainutils git && \ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y autoconf bsdmainutils git && \ curl https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz | tar -xzf - && \ mv go /usr/local diff --git a/package-deploy.yaml b/package-deploy.yaml index 871d6ef1f7..8daf262acc 100644 --- a/package-deploy.yaml +++ b/package-deploy.yaml @@ -30,8 +30,6 @@ agents: dockerFilePath: docker/build/aptly.Dockerfile image: algorand/aptly version: scripts/configure_dev-deps.sh - buildArgs: - - GOLANG_VERSION=`./scripts/get_golang_version.sh` env: - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY @@ -42,6 +40,8 @@ agents: volumes: - $XDG_RUNTIME_DIR/gnupg/S.gpg-agent:/root/.gnupg/S.gpg-agent - $HOME/.gnupg/pubring.kbx:/root/.gnupg/pubring.kbx + - $HOME/.gnupg/secring.gpg:/root/.gnupg/secring.gpg + - $HOME/.aptly:/root/.aptly workDir: $HOME/projects/go-algorand - name: rpm @@ -64,7 +64,6 @@ agents: - $HOME/.gnupg/pubring.kbx:/root/.gnupg/pubring.kbx workDir: $HOME/projects/go-algorand - tasks: - task: docker.Make name: docker diff --git a/scripts/release/mule/deploy/deb/deploy.sh b/scripts/release/mule/deploy/deb/deploy.sh index 395aa7060a..c9c4b4b6c4 100755 --- a/scripts/release/mule/deploy/deb/deploy.sh +++ b/scripts/release/mule/deploy/deb/deploy.sh @@ -32,34 +32,7 @@ aptly mirror update beta aptly repo import stable stable algorand algorand-devtools aptly repo import beta beta algorand-beta algorand-devtools-beta -KEY_PREFIX="$CHANNEL/$VERSION" -FILENAME_SUFFIX="${CHANNEL}_linux-amd64_${VERSION}.deb" -ALGORAND_KEY="$KEY_PREFIX/algorand_${FILENAME_SUFFIX}" -DEVTOOLS_KEY="$KEY_PREFIX/algorand-devtools_${FILENAME_SUFFIX}" - -# `STAGING` could contain a "path" (i.e. "my_bucket/foo/bar"), but the -# `s3api` api expects it to be only the bucket name (i.e., "my_bucket"). -BUCKET=$(awk -F/ '{ print $1 }' <<< "$STAGING") - -# If the strings match then the objects are in the top-level of the bucket. -if [ "$STAGING" = "$BUCKET" ] -then - BUCKET_PREFIX_PATH="$STAGING" -else - # Remove matching prefix. - BUCKET_PREFIX_PATH=${STAGING#$BUCKET"/"} -fi - -for key in {"$ALGORAND_KEY","$DEVTOOLS_KEY"} -do - key="$BUCKET_PREFIX_PATH/$key" - if aws s3api head-object --bucket "$BUCKET" --key "$key" - then - aws s3 cp "s3://$BUCKET/$key" "$PACKAGES_DIR" - else - echo "[$0] The package \`$key\` failed to download." - fi -done +cp -f tmp/{algorand,algorand-devtools}_${CHANNEL}_linux-{amd64,arm64}_${VERSION}.deb $PACKAGES_DIR if ls -A $PACKAGES_DIR then @@ -68,7 +41,7 @@ then aptly snapshot create "$SNAPSHOT" from repo "$CHANNEL" if ! aptly publish show "$CHANNEL" s3:algorand-releases: &> /dev/null then - aptly publish snapshot -gpg-key=dev@algorand.com -origin=Algorand -label=Algorand "$SNAPSHOT" s3:algorand-releases: + aptly publish -batch snapshot -gpg-key=dev@algorand.com -origin=Algorand -label=Algorand "$SNAPSHOT" s3:algorand-releases: else aptly publish switch "$CHANNEL" s3:algorand-releases: "$SNAPSHOT" fi @@ -76,4 +49,3 @@ else echo "[$0] The packages directory is empty, so there is nothing to add the \`$CHANNEL\` repo." exit 1 fi - From a45ac999453c2c3aa79da81dbb32e570466f0437 Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 19 Dec 2023 10:07:34 -0500 Subject: [PATCH 049/218] CICD: fix aptly repo management (#5870) --- .aptly.conf | 2 -- docker/build/aptly.Dockerfile | 24 +++++------------ docker/build/docker.ubuntu.Dockerfile | 14 ++++------ package-deploy.yaml | 5 ++-- scripts/release/mule/deploy/deb/deploy.sh | 32 ++--------------------- 5 files changed, 16 insertions(+), 61 deletions(-) diff --git a/.aptly.conf b/.aptly.conf index 6300abc3b7..8badff78da 100644 --- a/.aptly.conf +++ b/.aptly.conf @@ -1,5 +1,4 @@ { - "rootDir": "/root/aptly", "downloadConcurrency": 4, "downloadSpeedLimit": 0, "architectures": [], @@ -27,4 +26,3 @@ }, "SwiftPublishEndpoints": {} } - diff --git a/docker/build/aptly.Dockerfile b/docker/build/aptly.Dockerfile index 1849d8e5e1..24982e7412 100644 --- a/docker/build/aptly.Dockerfile +++ b/docker/build/aptly.Dockerfile @@ -1,26 +1,16 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG ARCH=amd64 -ARG GOLANG_VERSION -ENV DEBIAN_FRONTEND noninteractive -RUN apt-get update && apt-get install aptly awscli binutils build-essential curl gnupg2 -y -RUN curl https://dl.google.com/go/go${GOLANG_VERSION}.linux-${ARCH%v*}.tar.gz | tar -xzf - && mv go /usr/local -ENV GOROOT=/usr/local/go \ - GOPATH=/root/go \ - PATH=$GOPATH/bin:$GOROOT/bin:$PATH +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install aptly awscli binutils build-essential curl gnupg2 -y WORKDIR /root COPY .aptly.conf . -RUN curl https://releases.algorand.com/key.pub | gpg --no-default-keyring --keyring trustedkeys.gpg --import - && \ - aptly mirror create stable https://releases.algorand.com/deb/ stable main && \ +RUN curl https://releases.algorand.com/key.pub | gpg --no-default-keyring --keyring /root/.gnupg/trustedkeys.gpg --import - +RUN gpg --no-default-keyring --keyring /root/.gnupg/trustedkeys.gpg --export --output /root/.gnupg/newkeyring.gpg && mv -f /root/.gnupg/newkeyring.gpg /root/.gnupg/trustedkeys.gpg +RUN aptly mirror create stable https://releases.algorand.com/deb/ stable main && \ aptly mirror create beta https://releases.algorand.com/deb/ beta main && \ - aptly repo create -distribution=stable -architectures=amd64 -component=main -comment=mainnet stable && \ - aptly repo create -distribution=beta -architectures=amd64 -component=main -comment=betanet beta && \ - aptly mirror update stable && \ - aptly mirror update beta && \ - aptly repo import stable stable algorand algorand-devtools && \ - aptly repo import beta beta algorand-beta algorand-devtools-beta + aptly repo create -distribution=stable -architectures=amd64,arm64 -component=main -comment=mainnet stable && \ + aptly repo create -distribution=beta -architectures=amd64,arm64 -component=main -comment=betanet beta CMD ["/bin/bash"] - diff --git a/docker/build/docker.ubuntu.Dockerfile b/docker/build/docker.ubuntu.Dockerfile index 5091afefa9..e82dba3d71 100644 --- a/docker/build/docker.ubuntu.Dockerfile +++ b/docker/build/docker.ubuntu.Dockerfile @@ -3,18 +3,14 @@ ARG ARCH="amd64" FROM ${ARCH}/ubuntu:20.04 ARG GOLANG_VERSION ARG ARCH="amd64" -RUN apt-get update && apt-get install curl python python3.7 python3-pip build-essential apt-transport-https ca-certificates software-properties-common -y && \ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install curl python python3.7 python3-pip build-essential apt-transport-https ca-certificates software-properties-common -y && \ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \ - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \ - apt-get update && apt-get install docker-ce -y + DEBIAN_FRONTEND=noninteractive add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \ + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install docker-ce -y -# Mule needs >= python3.7 so set that as the default. -RUN update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 && \ - update-alternatives --install /usr/bin/python python /usr/bin/python3.7 2 && \ - update-alternatives --set python /usr/bin/python3.7 && \ - pip3 install mulecli +RUN pip3 install mulecli -RUN apt-get update && apt-get install -y autoconf bsdmainutils git && \ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y autoconf bsdmainutils git && \ curl https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz | tar -xzf - && \ mv go /usr/local diff --git a/package-deploy.yaml b/package-deploy.yaml index 871d6ef1f7..8daf262acc 100644 --- a/package-deploy.yaml +++ b/package-deploy.yaml @@ -30,8 +30,6 @@ agents: dockerFilePath: docker/build/aptly.Dockerfile image: algorand/aptly version: scripts/configure_dev-deps.sh - buildArgs: - - GOLANG_VERSION=`./scripts/get_golang_version.sh` env: - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY @@ -42,6 +40,8 @@ agents: volumes: - $XDG_RUNTIME_DIR/gnupg/S.gpg-agent:/root/.gnupg/S.gpg-agent - $HOME/.gnupg/pubring.kbx:/root/.gnupg/pubring.kbx + - $HOME/.gnupg/secring.gpg:/root/.gnupg/secring.gpg + - $HOME/.aptly:/root/.aptly workDir: $HOME/projects/go-algorand - name: rpm @@ -64,7 +64,6 @@ agents: - $HOME/.gnupg/pubring.kbx:/root/.gnupg/pubring.kbx workDir: $HOME/projects/go-algorand - tasks: - task: docker.Make name: docker diff --git a/scripts/release/mule/deploy/deb/deploy.sh b/scripts/release/mule/deploy/deb/deploy.sh index 395aa7060a..c9c4b4b6c4 100755 --- a/scripts/release/mule/deploy/deb/deploy.sh +++ b/scripts/release/mule/deploy/deb/deploy.sh @@ -32,34 +32,7 @@ aptly mirror update beta aptly repo import stable stable algorand algorand-devtools aptly repo import beta beta algorand-beta algorand-devtools-beta -KEY_PREFIX="$CHANNEL/$VERSION" -FILENAME_SUFFIX="${CHANNEL}_linux-amd64_${VERSION}.deb" -ALGORAND_KEY="$KEY_PREFIX/algorand_${FILENAME_SUFFIX}" -DEVTOOLS_KEY="$KEY_PREFIX/algorand-devtools_${FILENAME_SUFFIX}" - -# `STAGING` could contain a "path" (i.e. "my_bucket/foo/bar"), but the -# `s3api` api expects it to be only the bucket name (i.e., "my_bucket"). -BUCKET=$(awk -F/ '{ print $1 }' <<< "$STAGING") - -# If the strings match then the objects are in the top-level of the bucket. -if [ "$STAGING" = "$BUCKET" ] -then - BUCKET_PREFIX_PATH="$STAGING" -else - # Remove matching prefix. - BUCKET_PREFIX_PATH=${STAGING#$BUCKET"/"} -fi - -for key in {"$ALGORAND_KEY","$DEVTOOLS_KEY"} -do - key="$BUCKET_PREFIX_PATH/$key" - if aws s3api head-object --bucket "$BUCKET" --key "$key" - then - aws s3 cp "s3://$BUCKET/$key" "$PACKAGES_DIR" - else - echo "[$0] The package \`$key\` failed to download." - fi -done +cp -f tmp/{algorand,algorand-devtools}_${CHANNEL}_linux-{amd64,arm64}_${VERSION}.deb $PACKAGES_DIR if ls -A $PACKAGES_DIR then @@ -68,7 +41,7 @@ then aptly snapshot create "$SNAPSHOT" from repo "$CHANNEL" if ! aptly publish show "$CHANNEL" s3:algorand-releases: &> /dev/null then - aptly publish snapshot -gpg-key=dev@algorand.com -origin=Algorand -label=Algorand "$SNAPSHOT" s3:algorand-releases: + aptly publish -batch snapshot -gpg-key=dev@algorand.com -origin=Algorand -label=Algorand "$SNAPSHOT" s3:algorand-releases: else aptly publish switch "$CHANNEL" s3:algorand-releases: "$SNAPSHOT" fi @@ -76,4 +49,3 @@ else echo "[$0] The packages directory is empty, so there is nothing to add the \`$CHANNEL\` repo." exit 1 fi - From b2c158d8d9357d3cd100c2c39f763c819ebb4526 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 11 Dec 2023 12:32:19 -0500 Subject: [PATCH 050/218] AVM: Require every global field to get tested (#5864) --- data/transactions/logic/eval_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 2b85adbac1..05e5786013 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1232,6 +1232,7 @@ const globalV9TestProgram = globalV8TestProgram + ` const globalV10TestProgram = globalV9TestProgram + ` global AssetCreateMinBalance; int 1001; ==; && global AssetOptInMinBalance; int 1001; ==; && +global GenesisHash; len; int 32; ==; && ` const globalV11TestProgram = globalV10TestProgram + ` @@ -1265,6 +1266,10 @@ func TestGlobal(t *testing.T) { require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) require.Len(t, globalFieldSpecs, int(invalidGlobalField)) + // ensure we are testing everything + require.Equal(t, tests[AssemblerMaxVersion].lastField, invalidGlobalField-1, + "did you add a new global field?") + ledger := NewLedger(nil) addr, err := basics.UnmarshalChecksumAddress(testAddr) require.NoError(t, err) From 64f588912c37f8cad7c755a55007df754349a51b Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:06:20 -0500 Subject: [PATCH 051/218] tests: log all relay logs on e2e failure (#5881) --- netdeploy/network.go | 10 ++++++++++ test/framework/fixtures/libgoalFixture.go | 9 ++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/netdeploy/network.go b/netdeploy/network.go index 4e86bd831f..e4066e79ba 100644 --- a/netdeploy/network.go +++ b/netdeploy/network.go @@ -169,6 +169,16 @@ func (n Network) PrimaryDataDir() string { panic(fmt.Errorf("neither relay directories nor node directories are defined for the network")) } +// RelayDataDirs returns an array of relay data directories (not the nodes) +func (n Network) RelayDataDirs() []string { + var directories []string + for _, dir := range n.cfg.RelayDirs { + directories = append(directories, n.getNodeFullPath(dir)) + } + sort.Strings(directories) + return directories +} + // NodeDataDirs returns an array of node data directories (not the relays) func (n Network) NodeDataDirs() []string { var directories []string diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 3b54f9f7aa..58ca1662b3 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -317,7 +317,9 @@ func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { f.NC.StopKMD() if preserveData { f.network.Stop(f.binDir) - f.dumpLogs(filepath.Join(f.PrimaryDataDir(), "node.log")) + for _, relayDir := range f.RelayDataDirs() { + f.dumpLogs(filepath.Join(relayDir, "node.log")) + } for _, nodeDir := range f.NodeDataDirs() { f.dumpLogs(filepath.Join(nodeDir, "node.log")) } @@ -365,6 +367,11 @@ func (f *LibGoalFixture) PrimaryDataDir() string { return f.network.PrimaryDataDir() } +// RelayDataDirs returns the relays data directories for the network (including the primary relay) +func (f *LibGoalFixture) RelayDataDirs() []string { + return f.network.RelayDataDirs() +} + // NodeDataDirs returns the (non-Primary) data directories for the network func (f *LibGoalFixture) NodeDataDirs() []string { return f.network.NodeDataDirs() From 9eaebc0ef295b56ff966d50a312f6e77f9dcaa17 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:26:42 -0500 Subject: [PATCH 052/218] agreement: dump demux queues on fuzzer test failure (#5882) --- agreement/demux.go | 12 ++++++++++++ agreement/fuzzer/fuzzer_test.go | 7 +++++++ agreement/fuzzer/networkFacade_test.go | 4 ++-- agreement/fuzzer/validator_test.go | 6 ++++++ agreement/service.go | 6 ++++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/agreement/demux.go b/agreement/demux.go index 7f888dc97f..2653b8d8c1 100644 --- a/agreement/demux.go +++ b/agreement/demux.go @@ -19,6 +19,7 @@ package agreement import ( "context" "fmt" + "io" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" @@ -361,6 +362,17 @@ func (d *demux) next(s *Service, deadline Deadline, fastDeadline Deadline, curre return } +// dumpQueues dumps the current state of the demux queues to the given writer. +func (d *demux) dumpQueues(w io.Writer) { + fmt.Fprintf(w, "rawVotes: %d\n", len(d.rawVotes)) + fmt.Fprintf(w, "rawProposals: %d\n", len(d.rawProposals)) + fmt.Fprintf(w, "rawBundles: %d\n", len(d.rawBundles)) + + fmt.Fprintf(w, "cryptoVerifiedVotes: %d\n", len(d.crypto.VerifiedVotes())) + fmt.Fprintf(w, "cryptoVerified ProposalPayloadTag: %d\n", len(d.crypto.Verified(protocol.ProposalPayloadTag))) + fmt.Fprintf(w, "cryptoVerified VoteBundleTag: %d\n", len(d.crypto.Verified(protocol.VoteBundleTag))) +} + // setupCompoundMessage processes compound messages: distinct messages which are delivered together func setupCompoundMessage(l LedgerReader, m message) (res externalEvent) { compound := m.CompoundMessage diff --git a/agreement/fuzzer/fuzzer_test.go b/agreement/fuzzer/fuzzer_test.go index e505faa197..77a52401e5 100644 --- a/agreement/fuzzer/fuzzer_test.go +++ b/agreement/fuzzer/fuzzer_test.go @@ -261,6 +261,13 @@ func (n *Fuzzer) Start() { } } +// DumpQueues dumps the queues of all the nodes. +func (n *Fuzzer) DumpQueues() { + for _, f := range n.agreements { + f.DumpDemuxQueues(os.Stderr) + } +} + func (n *Fuzzer) InvokeFiltersShutdown(preshutdown bool) { for _, facade := range n.facades { dsFilter := facade.GetDownstreamFilter() diff --git a/agreement/fuzzer/networkFacade_test.go b/agreement/fuzzer/networkFacade_test.go index d18f8251dd..86c7ff7122 100644 --- a/agreement/fuzzer/networkFacade_test.go +++ b/agreement/fuzzer/networkFacade_test.go @@ -122,7 +122,7 @@ func (n *NetworkFacade) DumpQueues() { } n.eventsQueuesMu.Unlock() queues += "----------------------\n" - fmt.Printf(queues) + fmt.Print(queues) } func (n *NetworkFacade) WaitForEventsQueue(cleared bool) { @@ -151,7 +151,7 @@ func (n *NetworkFacade) WaitForEventsQueue(cleared bool) { n.DumpQueues() //panic("Waiting for event processing for 0 took too long") pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) - os.Exit(1) + panic(fmt.Sprintf("maxWait %d sec exceeded", maxEventQueueWait/time.Second)) } } diff --git a/agreement/fuzzer/validator_test.go b/agreement/fuzzer/validator_test.go index 87425a0997..8c7352ccc6 100644 --- a/agreement/fuzzer/validator_test.go +++ b/agreement/fuzzer/validator_test.go @@ -47,6 +47,12 @@ func (v *Validator) Go(netConfig *FuzzerConfig) { network := MakeFuzzer(*netConfig) require.NotNil(v.tb, network) + defer func() { + if r := recover(); r != nil { + network.DumpQueues() + } + }() + network.Start() //_, runRes := network.Run(v.config.NetworkRunDuration /*time.Millisecond*5000*/, time.Millisecond*3000, time.Second) _, v.runResult = network.Run(v.config.NetworkRunTicks, v.config.NetworkRecoverTicks, 100) diff --git a/agreement/service.go b/agreement/service.go index 433a17d5c6..852f171455 100644 --- a/agreement/service.go +++ b/agreement/service.go @@ -19,6 +19,7 @@ package agreement //go:generate dbgen -i agree.sql -p agreement -n agree -o agreeInstall.go -h ../scripts/LICENSE_HEADER import ( "context" + "io" "time" "github.com/algorand/go-algorand/config" @@ -178,6 +179,11 @@ func (s *Service) Shutdown() { s.persistenceLoop.Quit() } +// DumpDemuxQueues dumps the demux queues to the given writer. +func (s *Service) DumpDemuxQueues(w io.Writer) { + s.demux.dumpQueues(w) +} + // demuxLoop repeatedly executes pending actions and then requests the next event from the Service.demux. func (s *Service) demuxLoop(ctx context.Context, input chan<- externalEvent, output <-chan []action, ready <-chan externalDemuxSignals) { for a := range output { From a4fcdfa0d216fe9b0a60d57acc76e92131cfbfbd Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 21 Dec 2023 11:04:41 -0500 Subject: [PATCH 053/218] AVM: Don't treat `any` as constant int in `loads`, `stores` (#5884) --- data/transactions/logic/assembler.go | 4 ++-- data/transactions/logic/assembler_test.go | 16 ++++++++++++++++ data/transactions/logic/eval.go | 8 ++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index e8011fd810..182b3e9dd3 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1481,7 +1481,7 @@ func typeStores(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, er // If the index of the scratch slot is a const // we can modify only that scratch slots type if top >= 1 { - if idx, isConst := pgm.stack[top-1].constant(); isConst { + if idx, isConst := pgm.stack[top-1].constInt(); isConst { pgm.scratchSpace[idx] = pgm.stack[top] return nil, nil, nil } @@ -1524,7 +1524,7 @@ func typeLoads(pgm *ProgramKnowledge, args []token) (StackTypes, StackTypes, err return nil, nil, nil } - if val, isConst := pgm.stack[top].constant(); isConst { + if val, isConst := pgm.stack[top].constInt(); isConst { return nil, StackTypes{pgm.scratchSpace[val]}, nil } diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index c45c0af77d..ffa7598c8f 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -2955,6 +2955,22 @@ done: `, LogicVersion, exp(5, "concat arg 1 wanted type []byte...")) } +func TestTypeTrackingRegression(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + testProg(t, ` +callsub end // wipes out initial program knowledge, makes scratch "any" +label1: + load 1 + byte 0x01 + stores // we had a bug in which the "any" seemed constant + load 0 + load 0 + + +end: +`, LogicVersion) +} + func TestMergeProtos(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 836b8cb852..971fffa72d 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -879,11 +879,11 @@ func (st StackType) widened() StackType { } } -func (st StackType) constant() (uint64, bool) { - if st.Bound[0] == st.Bound[1] { - return st.Bound[0], true +func (st StackType) constInt() (uint64, bool) { + if st.AVMType != avmUint64 || st.Bound[0] != st.Bound[1] { + return 0, false } - return 0, false + return st.Bound[0], true } // overlaps checks if there is enough overlap From 55cbb7f6d93acbe5040e4530aaabb87aec729f13 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:19:27 -0500 Subject: [PATCH 054/218] txHandler: kick in ARL at 1/2 of a base backlog capacity (#5873) --- data/txHandler.go | 19 +++++++++++-------- data/txHandler_test.go | 12 ++++++------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/data/txHandler.go b/data/txHandler.go index 9e6e1094c4..58febd1d76 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -131,6 +131,7 @@ type TxHandler struct { streamVerifierDropped chan *verify.UnverifiedTxnSigJob erl *util.ElasticRateLimiter appLimiter *appRateLimiter + appLimiterBacklogThreshold int } // TxHandlerOpts is TxHandler configuration options @@ -203,6 +204,8 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { uint64(opts.Config.TxBacklogAppTxPerSecondRate), time.Duration(opts.Config.TxBacklogServiceRateWindowSeconds)*time.Second, ) + // set appLimiter triggering threshold at 50% of the base backlog size + handler.appLimiterBacklogThreshold = int(float64(opts.Config.TxBacklogSize) * float64(opts.Config.TxBacklogRateLimitingCongestionPct) / 100) } } @@ -596,11 +599,8 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net var err error var capguard *util.ErlCapacityGuard - var congested bool - if handler.erl != nil || handler.appLimiter != nil { - congested = float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue)) - } if handler.erl != nil { + congestedERL := float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue)) // consume a capacity unit // if the elastic rate limiter cannot vend a capacity, the error it returns // is sufficient to indicate that we should enable Congestion Control, because @@ -613,7 +613,7 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net return network.OutgoingMessage{Action: network.Ignore} } // if the backlog Queue has 50% of its buffer back, turn congestion control off - if !congested { + if !congestedERL { handler.erl.DisableCongestionControl() } } @@ -663,9 +663,12 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net } // rate limit per application in a group. Limiting any app in a group drops the entire message. - if handler.appLimiter != nil && congested && handler.appLimiter.shouldDrop(unverifiedTxGroup, rawmsg.Sender.(network.IPAddressable).RoutingAddr()) { - transactionMessagesAppLimiterDrop.Inc(nil) - return network.OutgoingMessage{Action: network.Ignore} + if handler.appLimiter != nil { + congestedARL := len(handler.backlogQueue) > handler.appLimiterBacklogThreshold + if congestedARL && handler.appLimiter.shouldDrop(unverifiedTxGroup, rawmsg.Sender.(network.IPAddressable).RoutingAddr()) { + transactionMessagesAppLimiterDrop.Inc(nil) + return network.OutgoingMessage{Action: network.Ignore} + } } select { diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 894fef9d4d..74282b5c22 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -2522,9 +2522,7 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { cfg.TxBacklogAppTxRateLimiterMaxSize = 100 cfg.TxBacklogServiceRateWindowSeconds = 1 cfg.TxBacklogAppTxPerSecondRate = 3 - cfg.TxBacklogReservedCapacityPerPeer = 2 - cfg.TxBacklogSize = 1 - cfg.IncomingConnectionsLimit = 1 + cfg.TxBacklogSize = 3 ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, bookkeeping.GenesisBalances{}, genesisID, genesisHash, nil, cfg) require.NoError(t, err) defer ledger.Close() @@ -2585,7 +2583,9 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { sender := mockSender{} // submit and ensure it is accepted - congested := float64(cap(handler.backlogQueue))*0.5 < float64(len(handler.backlogQueue)) + pct := float64(cfg.TxBacklogRateLimitingCongestionPct) / 100 + limit := int(float64(cfg.TxBacklogSize) * pct) + congested := len(handler.backlogQueue) > limit require.False(t, congested) action := handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender}) @@ -2593,7 +2593,7 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { require.Equal(t, 1, len(handler.backlogQueue)) // repeat the same txn, we are still not congested - congested = float64(cap(handler.backlogQueue))*0.5 < float64(len(handler.backlogQueue)) + congested = len(handler.backlogQueue) > limit require.False(t, congested) signedTx = tx.Sign(keypair()) @@ -2603,7 +2603,7 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { require.Equal(t, 2, len(handler.backlogQueue)) require.Equal(t, 0, handler.appLimiter.len()) // no rate limiting yet - congested = float64(cap(handler.backlogQueue))*0.5 < float64(len(handler.backlogQueue)) + congested = len(handler.backlogQueue) > limit require.True(t, congested) // submit it again and the app rate limiter should kick in From 3a80a406f7b94161ce031bec00b5a82bdf9becba Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:58:22 -0500 Subject: [PATCH 055/218] tests: rewrite triggerTrackerFlush test helper (#5876) --- ledger/catchpointtracker.go | 15 ++++-- ledger/ledger_test.go | 92 +++++++++++++++++-------------------- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index 32216fcb20..7745e33f55 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -151,8 +151,8 @@ type catchpointTracker struct { // catchpoint files even before the protocol upgrade took place. forceCatchpointFileWriting bool - // catchpointsMu protects roundDigest, reenableCatchpointsRound, cachedDBRound and - // `lastCatchpointLabel`. + // catchpointsMu protects roundDigest, reenableCatchpointsRound, cachedDBRound, + // lastCatchpointLabel and balancesTrie. catchpointsMu deadlock.RWMutex // cachedDBRound is always exactly tracker DB round (and therefore, accountsRound()), @@ -555,16 +555,19 @@ func (ct *catchpointTracker) commitRound(ctx context.Context, tx trackerdb.Trans } var trie *merkletrie.Trie + ct.catchpointsMu.Lock() if ct.balancesTrie == nil { trie, err = merkletrie.MakeTrie(mc, trackerdb.TrieMemoryConfig) if err != nil { ct.log.Warnf("unable to create merkle trie during committedUpTo: %v", err) + ct.catchpointsMu.Unlock() return err } ct.balancesTrie = trie } else { ct.balancesTrie.SetCommitter(mc) } + ct.catchpointsMu.Unlock() treeTargetRound = dbRound + basics.Round(offset) } @@ -610,6 +613,7 @@ func (ct *catchpointTracker) commitRound(ctx context.Context, tx trackerdb.Trans } func (ct *catchpointTracker) postCommit(ctx context.Context, dcc *deferredCommitContext) { + ct.catchpointsMu.Lock() if ct.balancesTrie != nil { _, err := ct.balancesTrie.Evict(false) if err != nil { @@ -617,7 +621,6 @@ func (ct *catchpointTracker) postCommit(ctx context.Context, dcc *deferredCommit } } - ct.catchpointsMu.Lock() ct.roundDigest = ct.roundDigest[dcc.offset:] ct.consensusVersion = ct.consensusVersion[dcc.offset:] ct.cachedDBRound = dcc.newBase() @@ -986,7 +989,9 @@ func (ct *catchpointTracker) handleCommitError(dcc *deferredCommitContext) { // Specifically, modifications to the trie happen through accountsUpdateBalances, // which happens before commit to disk. Errors in this tracker, subsequent trackers, or the commit to disk may cause the trie cache to be incorrect, // affecting the perceived root on subsequent rounds + ct.catchpointsMu.Lock() ct.balancesTrie = nil + ct.catchpointsMu.Unlock() ct.cancelWrite(dcc) } @@ -1276,9 +1281,11 @@ func (ct *catchpointTracker) recordFirstStageInfo(ctx context.Context, tx tracke if err != nil { return err } + ct.catchpointsMu.Lock() if ct.balancesTrie == nil { trie, trieErr := merkletrie.MakeTrie(mc, trackerdb.TrieMemoryConfig) if trieErr != nil { + ct.catchpointsMu.Unlock() return trieErr } ct.balancesTrie = trie @@ -1288,8 +1295,10 @@ func (ct *catchpointTracker) recordFirstStageInfo(ctx context.Context, tx tracke trieBalancesHash, err := ct.balancesTrie.RootHash() if err != nil { + ct.catchpointsMu.Unlock() return err } + ct.catchpointsMu.Unlock() cw, err := tx.MakeCatchpointWriter() if err != nil { diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 19bb6a0796..172643d48c 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -27,7 +27,6 @@ import ( "runtime" "sort" "testing" - "time" "github.com/stretchr/testify/require" "golang.org/x/exp/slices" @@ -1443,30 +1442,43 @@ func benchLedgerCache(b *testing.B, startRound basics.Round) { } } -func triggerTrackerFlush(t *testing.T, l *Ledger, genesisInitState ledgercore.InitState) { +// triggerTrackerFlush is based in the commit flow but executed it in a single (this) goroutine. +func triggerTrackerFlush(t *testing.T, l *Ledger) { l.trackers.mu.Lock() - initialDbRound := l.trackers.dbRound - currentDbRound := initialDbRound - l.trackers.lastFlushTime = time.Time{} + dbRound := l.trackers.dbRound l.trackers.mu.Unlock() - const timeout = 3 * time.Second - started := time.Now() + rnd := l.Latest() + minBlock := rnd + maxLookback := basics.Round(0) + for _, lt := range l.trackers.trackers { + retainRound, lookback := lt.committedUpTo(rnd) + if retainRound < minBlock { + minBlock = retainRound + } + if lookback > maxLookback { + maxLookback = lookback + } + } - // We can't truly wait for scheduleCommit to take place, which means without waiting using sleeps - // we might beat scheduleCommit's addition to accountsWriting, making our wait on it continue immediately. - // The solution is to continue to add blocks and wait for the advancement of l.trackers.dbRound, - // which is a side effect of postCommit's success. - for currentDbRound == initialDbRound { - time.Sleep(50 * time.Microsecond) - require.True(t, time.Since(started) < timeout) - addEmptyValidatedBlock(t, l, genesisInitState.Accounts) - l.WaitForCommit(l.Latest()) - l.trackers.mu.RLock() - currentDbRound = l.trackers.dbRound - l.trackers.mu.RUnlock() + dcc := &deferredCommitContext{ + deferredCommitRange: deferredCommitRange{ + lookback: maxLookback, + }, + } + + l.trackers.mu.RLock() + cdr := l.trackers.produceCommittingTask(rnd, dbRound, &dcc.deferredCommitRange) + if cdr != nil { + dcc.deferredCommitRange = *cdr + } else { + dcc = nil + } + l.trackers.mu.RUnlock() + if dcc != nil { + l.trackers.accountsWriting.Add(1) + l.trackers.commitRound(dcc) } - l.trackers.waitAccountsWriting() } func testLedgerReload(t *testing.T, cfg config.Local) { @@ -1646,7 +1658,7 @@ func TestLedgerVerifiesOldStateProofs(t *testing.T) { backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) defer backlogPool.Shutdown() - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) l.WaitForCommit(l.Latest()) blk := createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) _, err = l.Validate(context.Background(), blk, backlogPool) @@ -1656,7 +1668,7 @@ func TestLedgerVerifiesOldStateProofs(t *testing.T) { addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) } - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) l.WaitForCommit(l.Latest()) // At this point the block queue go-routine will start removing block . However, it might not complete the task @@ -2767,11 +2779,11 @@ func verifyVotersContent(t *testing.T, expected map[basics.Round]*ledgercore.Vot func triggerDeleteVoters(t *testing.T, l *Ledger, genesisInitState ledgercore.InitState) { // We make the ledger flush tracker data to allow votersTracker to advance lowestRound - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) // We add another block to make the block queue query the voter's tracker lowest round again, which allows it to forget // rounds based on the new lowest round. - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) } func testVotersReloadFromDisk(t *testing.T, cfg config.Local) { @@ -2796,7 +2808,7 @@ func testVotersReloadFromDisk(t *testing.T, cfg config.Local) { // at this point the database should contain the voter for round 256 but the voters for round 512 should be in deltas l.WaitForCommit(l.Latest()) - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) vtSnapshot := l.acctsOnline.voters.votersForRoundCache // ensuring no tree was evicted. @@ -3028,7 +3040,7 @@ func TestLedgerSPVerificationTracker(t *testing.T) { } l.WaitForCommit(l.Latest()) - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), numOfStateProofs-1, proto.StateProofInterval, true, trackerDB) @@ -3037,7 +3049,7 @@ func TestLedgerSPVerificationTracker(t *testing.T) { 1, proto.StateProofInterval, true, trackerMemory) l.WaitForCommit(l.Latest()) - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), numOfStateProofs, proto.StateProofInterval, true, spverDBLoc) @@ -3063,7 +3075,7 @@ func TestLedgerSPVerificationTracker(t *testing.T) { } l.WaitForCommit(blk.BlockHeader.Round) - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), 1, proto.StateProofInterval, false, spverDBLoc) @@ -3101,16 +3113,7 @@ func TestLedgerReloadStateProofVerificationTracker(t *testing.T) { // trigger trackers flush // first ensure the block is committed into blockdb l.WaitForCommit(l.Latest()) - // wait for any pending tracker flushes - l.trackers.waitAccountsWriting() - // force flush as needed - if l.LatestTrackerCommitted() < l.Latest()+basics.Round(cfg.MaxAcctLookback) { - l.trackers.mu.Lock() - l.trackers.lastFlushTime = time.Time{} - l.trackers.mu.Unlock() - l.notifyCommit(l.Latest()) - l.trackers.waitAccountsWriting() - } + triggerTrackerFlush(t, l) verifyStateProofVerificationTracking(t, &l.spVerification, basics.Round(firstStateProofContextTargetRound), numOfStateProofs-1, proto.StateProofInterval, true, trackerDB) @@ -3167,7 +3170,7 @@ func TestLedgerCatchpointSPVerificationTracker(t *testing.T) { // Feeding blocks until we can know for sure we have at least one catchpoint written. blk = feedBlocksUntilRound(t, l, blk, basics.Round(cfg.CatchpointInterval*2)) l.WaitForCommit(basics.Round(cfg.CatchpointInterval * 2)) - triggerTrackerFlush(t, l, genesisInitState) + triggerTrackerFlush(t, l) numTrackedDataFirstCatchpoint := (cfg.CatchpointInterval - proto.MaxBalLookback) / proto.StateProofInterval @@ -3244,16 +3247,7 @@ func TestLedgerSPTrackerAfterReplay(t *testing.T) { // first ensure the block is committed into blockdb l.WaitForCommit(l.Latest()) - // wait for any pending tracker flushes - l.trackers.waitAccountsWriting() - // force flush as needed - if l.LatestTrackerCommitted() < l.Latest()+basics.Round(cfg.MaxAcctLookback) { - l.trackers.mu.Lock() - l.trackers.lastFlushTime = time.Time{} - l.trackers.mu.Unlock() - l.notifyCommit(spblk.BlockHeader.Round) - l.trackers.waitAccountsWriting() - } + triggerTrackerFlush(t, l) err = l.reloadLedger() a.NoError(err) From 6d6f028446b96b42805f5e3b516d902117dcdc30 Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 2 Jan 2024 17:28:13 -0500 Subject: [PATCH 056/218] Chore: Update check_license script to update copyright year (#5888) --- scripts/check_license.sh | 66 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/scripts/check_license.sh b/scripts/check_license.sh index 51f83d7d4f..f61e933967 100755 --- a/scripts/check_license.sh +++ b/scripts/check_license.sh @@ -3,8 +3,22 @@ PROJECT_ROOT=$(git rev-parse --show-toplevel) LICENSE_LOCATION="$PROJECT_ROOT"/scripts/LICENSE_HEADER NUMLINES=$(< "$LICENSE_LOCATION" wc -l | tr -d ' ') -LICENSE=$(sed "s/{DATE_Y}/$(date +"%Y")/" "$LICENSE_LOCATION") +CURRENT_YEAR=$(date +"%Y") +LICENSE=$(sed "s/{DATE_Y}/$CURRENT_YEAR/" "$LICENSE_LOCATION") VERSIONED_GO_FILES=$(git ls-tree --full-tree --name-only -r HEAD | grep "\.go$") +EXTRA_FILES=( + cmd/tealdbg/bundle_home_html.sh + crypto/memcpy_chk_windows.c + tools/x-repo-types/typeAnalyzer/main.tmpl + test/heapwatch/block_history.py + test/heapwatch/block_history_plot.py + test/heapwatch/metrics_delta.py + test/heapwatch/nodeHostTarget.py + test/heapwatch/client_ram_report.py + test/heapwatch/runNodeHost.py + test/heapwatch/block_history_relays.py + test/heapwatch/heapWatch.py +) EXCLUDE=( "Code generated by" "David Lazar" @@ -16,6 +30,7 @@ EXCLUDE=( ) FILTER=$(IFS="|" ; echo "${EXCLUDE[*]}") INPLACE=false +UPDATE=false VERBOSE=false MOD_COUNT=0 RETURN_VALUE=0 @@ -27,6 +42,7 @@ usage() { echo echo "Args:" echo "-i Edit in-place." + echo "-u Update license to current year." echo "-v Verbose, same as doing \`head -n ${NUMLINES:-15}\` on each file." echo } @@ -39,6 +55,9 @@ while [ "$1" != "" ]; do -i) INPLACE=true ;; + -u) + UPDATE=true + ;; -v) VERBOSE=true ;; -h) @@ -62,7 +81,11 @@ for FILE in $VERSIONED_GO_FILES; do RETURN_VALUE=1 if ! $VERBOSE; then - if $INPLACE; then + if $UPDATE; then + sed -i.orig s/Copyright\ \(C\)\ 2019-....\ Algorand,\ Inc\./Copyright\ \(C\)\ 2019-$CURRENT_YEAR\ Algorand,\ Inc./ "$PROJECT_ROOT/$FILE" && \ + rm "$PROJECT_ROOT/$FILE".orig + ((MOD_COUNT++)) + elif $INPLACE; then cat <(echo -e "$LICENSE\n") "$PROJECT_ROOT/$FILE" > "$PROJECT_ROOT/$FILE".1 && mv "$PROJECT_ROOT/$FILE"{.1,} ((MOD_COUNT++)) @@ -77,17 +100,48 @@ for FILE in $VERSIONED_GO_FILES; do fi done +# non-go files that include a license header +for FILE in "${EXTRA_FILES[@]}"; do + if ! grep -qs "Copyright (C) 2019-$CURRENT_YEAR Algorand, Inc." "$PROJECT_ROOT/$FILE"; then + RETURN_VALUE=1 + if ! $VERBOSE; then + if $UPDATE; then + sed -i.orig s/Copyright\ \(C\)\ 2019-....\ Algorand,\ Inc\./Copyright\ \(C\)\ 2019-$CURRENT_YEAR\ Algorand,\ Inc./ "$PROJECT_ROOT/$FILE" && \ + rm "$PROJECT_ROOT/$FILE".orig + ((MOD_COUNT++)) + fi + # It's dangerous to do inplace updates of non-go files, because their format might be different + echo "$FILE" + else + echo -e "\n${RED_FG}$FILE${END_FG_COLOR}" + <"$PROJECT_ROOT/$FILE" head -n "$NUMLINES" + echo + fi + fi +done + # check the README.md file. -READMECOPYRIGHT="Copyright (C) 2019-$(date +"%Y"), Algorand Inc." +READMECOPYRIGHT="Copyright (C) 2019-$CURRENT_YEAR, Algorand Inc." if [ "$( Date: Wed, 3 Jan 2024 10:31:03 -0500 Subject: [PATCH 057/218] Chore: Update copyright to 2024 (#5890) --- README.md | 2 +- agreement/abstractions.go | 2 +- agreement/actions.go | 2 +- agreement/actor.go | 2 +- agreement/agreeInstall.go | 2 +- agreement/agreementtest/keyManager.go | 2 +- agreement/agreementtest/simulate.go | 2 +- agreement/agreementtest/simulate_test.go | 2 +- agreement/asyncVoteVerifier.go | 2 +- agreement/asyncVoteVerifier_test.go | 2 +- agreement/autopsy.go | 2 +- agreement/bundle.go | 2 +- agreement/bundle_test.go | 2 +- agreement/cadaver.go | 2 +- agreement/certificate.go | 2 +- agreement/certificate_test.go | 2 +- agreement/common_test.go | 2 +- agreement/coservice.go | 2 +- agreement/credentialArrivalHistory.go | 2 +- agreement/credentialArrivalHistory_test.go | 2 +- agreement/cryptoRequestContext.go | 2 +- agreement/cryptoRequestContext_test.go | 2 +- agreement/cryptoVerifier.go | 2 +- agreement/cryptoVerifier_test.go | 2 +- agreement/demux.go | 2 +- agreement/demux_test.go | 2 +- agreement/doc.go | 2 +- agreement/dynamicFilterTimeoutParams.go | 2 +- agreement/dynamicFilterTimeoutParams_test.go | 2 +- agreement/encoding_test.go | 2 +- agreement/errors.go | 2 +- agreement/events.go | 2 +- agreement/events_test.go | 2 +- agreement/fuzzer/bandwidthFilter_test.go | 2 +- agreement/fuzzer/catchupFilter_test.go | 2 +- agreement/fuzzer/clockedFilter_test.go | 2 +- agreement/fuzzer/dropMessageFilter_test.go | 2 +- agreement/fuzzer/duplicateMessageFilter_test.go | 2 +- agreement/fuzzer/filter_test.go | 2 +- agreement/fuzzer/fuzzer.go | 2 +- agreement/fuzzer/fuzzer_test.go | 2 +- agreement/fuzzer/ledger_test.go | 2 +- agreement/fuzzer/messageDecoderFilter_test.go | 2 +- agreement/fuzzer/messageDelayFilter_test.go | 2 +- agreement/fuzzer/messageDuplicationFilter_test.go | 2 +- agreement/fuzzer/messagePriorityQueue_test.go | 2 +- agreement/fuzzer/messageReflectionFilter_test.go | 2 +- agreement/fuzzer/messageRegossipFilter_test.go | 2 +- agreement/fuzzer/messageReorderingFilter_test.go | 2 +- agreement/fuzzer/networkFacade_test.go | 2 +- agreement/fuzzer/nodeCrashFilter_test.go | 2 +- agreement/fuzzer/nullFilter_test.go | 2 +- agreement/fuzzer/router_test.go | 2 +- agreement/fuzzer/schedulerFilter_test.go | 2 +- agreement/fuzzer/tests_test.go | 2 +- agreement/fuzzer/topologyFilter_test.go | 2 +- agreement/fuzzer/trafficStatisticsFilter_test.go | 2 +- agreement/fuzzer/validator_test.go | 2 +- agreement/fuzzer/voteFilter_test.go | 2 +- agreement/gossip/network.go | 2 +- agreement/gossip/networkFull_test.go | 2 +- agreement/gossip/network_test.go | 2 +- agreement/keyManager_test.go | 2 +- agreement/listener.go | 2 +- agreement/message.go | 2 +- agreement/message_test.go | 2 +- agreement/params.go | 2 +- agreement/persistence.go | 2 +- agreement/persistence_test.go | 2 +- agreement/player.go | 2 +- agreement/playerContract.go | 2 +- agreement/player_permutation_test.go | 2 +- agreement/player_test.go | 2 +- agreement/proposal.go | 2 +- agreement/proposalManager.go | 2 +- agreement/proposalManagerContract.go | 2 +- agreement/proposalManager_test.go | 2 +- agreement/proposalStore.go | 2 +- agreement/proposalStoreContract.go | 2 +- agreement/proposalStore_test.go | 2 +- agreement/proposalTable.go | 2 +- agreement/proposalTable_test.go | 2 +- agreement/proposalTracker.go | 2 +- agreement/proposalTrackerContract.go | 2 +- agreement/proposalTracker_test.go | 2 +- agreement/proposal_test.go | 2 +- agreement/pseudonode.go | 2 +- agreement/pseudonode_test.go | 2 +- agreement/router.go | 2 +- agreement/selector.go | 2 +- agreement/service.go | 2 +- agreement/service_test.go | 2 +- agreement/sort.go | 2 +- agreement/sort_test.go | 2 +- agreement/state_machine_test.go | 2 +- agreement/trace.go | 2 +- agreement/traceTime.go | 2 +- agreement/types.go | 2 +- agreement/vote.go | 2 +- agreement/voteAggregator.go | 2 +- agreement/voteAggregatorContract.go | 2 +- agreement/voteAggregator_test.go | 2 +- agreement/voteAuxiliary.go | 2 +- agreement/voteAuxiliaryContract.go | 2 +- agreement/voteAuxiliary_test.go | 2 +- agreement/voteTracker.go | 2 +- agreement/voteTrackerContract.go | 2 +- agreement/voteTracker_test.go | 2 +- agreement/vote_test.go | 2 +- catchup/catchpointService.go | 2 +- catchup/catchpointService_test.go | 2 +- catchup/fetcher_test.go | 2 +- catchup/ledgerFetcher.go | 2 +- catchup/ledgerFetcher_test.go | 2 +- catchup/peerSelector.go | 2 +- catchup/peerSelector_test.go | 2 +- catchup/pref_test.go | 2 +- catchup/service.go | 2 +- catchup/service_test.go | 2 +- catchup/universalFetcher.go | 2 +- catchup/universalFetcher_test.go | 2 +- cmd/algocfg/getCommand.go | 2 +- cmd/algocfg/getCommand_test.go | 2 +- cmd/algocfg/main.go | 2 +- cmd/algocfg/profileCommand.go | 2 +- cmd/algocfg/profileCommand_test.go | 2 +- cmd/algocfg/report.go | 2 +- cmd/algocfg/resetCommand.go | 2 +- cmd/algocfg/setCommand.go | 2 +- cmd/algod/main.go | 2 +- cmd/algod/main_test.go | 2 +- cmd/algofix/deadlock.go | 2 +- cmd/algofix/deadlock_test.go | 2 +- cmd/algoh/blockWatcher.go | 2 +- cmd/algoh/blockWatcher_test.go | 2 +- cmd/algoh/blockstats.go | 2 +- cmd/algoh/blockstats_test.go | 2 +- cmd/algoh/client.go | 2 +- cmd/algoh/deadman.go | 2 +- cmd/algoh/eventsender.go | 2 +- cmd/algoh/main.go | 2 +- cmd/algoh/mockClient.go | 2 +- cmd/algokey/common.go | 2 +- cmd/algokey/export.go | 2 +- cmd/algokey/generate.go | 2 +- cmd/algokey/import.go | 2 +- cmd/algokey/keyreg.go | 2 +- cmd/algokey/main.go | 2 +- cmd/algokey/multisig.go | 2 +- cmd/algokey/part.go | 2 +- cmd/algokey/sign.go | 2 +- cmd/algons/commands.go | 2 +- cmd/algons/dnsCmd.go | 2 +- cmd/algons/dnsaddrCmd.go | 2 +- cmd/algorelay/commands.go | 2 +- cmd/algorelay/eb/eb.go | 2 +- cmd/algorelay/relayCmd.go | 2 +- cmd/buildtools/commands.go | 2 +- cmd/buildtools/genesis.go | 2 +- cmd/catchpointdump/commands.go | 2 +- cmd/catchpointdump/database.go | 2 +- cmd/catchpointdump/file.go | 2 +- cmd/catchpointdump/net.go | 2 +- cmd/catchupsrv/download.go | 2 +- cmd/catchupsrv/download_test.go | 2 +- cmd/catchupsrv/main.go | 2 +- cmd/catchupsrv/tarblocks.go | 2 +- cmd/dbgen/main.go | 2 +- cmd/diagcfg/main.go | 2 +- cmd/diagcfg/messages.go | 2 +- cmd/diagcfg/metric.go | 2 +- cmd/diagcfg/telemetry.go | 2 +- cmd/dispenser/server.go | 2 +- cmd/genesis/newgenesis.go | 2 +- cmd/goal/account.go | 2 +- cmd/goal/accountsList.go | 2 +- cmd/goal/application.go | 2 +- cmd/goal/application_test.go | 2 +- cmd/goal/asset.go | 2 +- cmd/goal/box.go | 2 +- cmd/goal/clerk.go | 2 +- cmd/goal/clerk_test.go | 2 +- cmd/goal/commands.go | 2 +- cmd/goal/commands_test.go | 2 +- cmd/goal/common.go | 2 +- cmd/goal/completion.go | 2 +- cmd/goal/formatting.go | 2 +- cmd/goal/formatting_test.go | 2 +- cmd/goal/inspect.go | 2 +- cmd/goal/inspect_test.go | 2 +- cmd/goal/interact.go | 2 +- cmd/goal/kmd.go | 2 +- cmd/goal/ledger.go | 2 +- cmd/goal/logging.go | 2 +- cmd/goal/messages.go | 2 +- cmd/goal/multisig.go | 2 +- cmd/goal/network.go | 2 +- cmd/goal/node.go | 2 +- cmd/goal/node_test.go | 2 +- cmd/goal/tealsign.go | 2 +- cmd/goal/wallet.go | 2 +- cmd/incorporate/incorporate.go | 2 +- cmd/kmd/codes/codes.go | 2 +- cmd/kmd/main.go | 2 +- cmd/kmd/mlock_darwin.go | 2 +- cmd/kmd/mlock_linux.go | 2 +- cmd/kmd/mlock_windows.go | 2 +- cmd/loadgenerator/config.go | 2 +- cmd/loadgenerator/main.go | 2 +- cmd/msgpacktool/main.go | 2 +- cmd/netdummy/main.go | 2 +- cmd/netgoal/commands.go | 2 +- cmd/netgoal/generate.go | 2 +- cmd/netgoal/messages.go | 2 +- cmd/netgoal/network.go | 2 +- cmd/netgoal/recipe.go | 2 +- cmd/nodecfg/apply.go | 2 +- cmd/nodecfg/commands.go | 2 +- cmd/nodecfg/download.go | 2 +- cmd/nodecfg/get.go | 2 +- cmd/opdoc/opdoc.go | 2 +- cmd/opdoc/tmLanguage.go | 2 +- cmd/partitiontest_linter/linter.go | 2 +- cmd/partitiontest_linter/linter_test.go | 2 +- cmd/partitiontest_linter/plugin/plugin.go | 2 +- cmd/partitiontest_linter/testdata/linter_testdata_test.go | 2 +- cmd/pingpong/commands.go | 2 +- cmd/pingpong/runCmd.go | 2 +- cmd/pingpong/teal_programs.go | 2 +- cmd/tealdbg/bundle_home_html.sh | 2 +- cmd/tealdbg/cdt/proto.go | 2 +- cmd/tealdbg/cdtSession.go | 2 +- cmd/tealdbg/cdtSession_test.go | 2 +- cmd/tealdbg/cdtState.go | 2 +- cmd/tealdbg/cdtStateObjects.go | 2 +- cmd/tealdbg/cdtdbg.go | 2 +- cmd/tealdbg/cdtdbg_test.go | 2 +- cmd/tealdbg/debugger.go | 2 +- cmd/tealdbg/debugger_test.go | 2 +- cmd/tealdbg/dryrunRequest.go | 2 +- cmd/tealdbg/homepage.go | 2 +- cmd/tealdbg/local.go | 2 +- cmd/tealdbg/localLedger.go | 2 +- cmd/tealdbg/localLedger_test.go | 2 +- cmd/tealdbg/local_test.go | 2 +- cmd/tealdbg/main.go | 2 +- cmd/tealdbg/remote.go | 2 +- cmd/tealdbg/remote_test.go | 2 +- cmd/tealdbg/server.go | 2 +- cmd/tealdbg/server_test.go | 2 +- cmd/tealdbg/util.go | 2 +- cmd/tealdbg/webdbg.go | 2 +- cmd/tealdbg/webdbg_test.go | 2 +- cmd/updater/commands.go | 2 +- cmd/updater/sendCmd.go | 2 +- cmd/updater/toolsCmd.go | 2 +- cmd/updater/util.go | 2 +- cmd/updater/versionCmd.go | 2 +- cmd/updater/version_test.go | 2 +- cmd/util/cmd.go | 2 +- cmd/util/datadir/datadir.go | 2 +- cmd/util/datadir/messages.go | 2 +- cmd/util/datadir/report.go | 2 +- components/mocks/mockCatchpointCatchupAccessor.go | 2 +- components/mocks/mockNetwork.go | 2 +- components/mocks/mockParticipationRegistry.go | 2 +- config/buildvars.go | 2 +- config/config.go | 2 +- config/config_test.go | 2 +- config/consensus.go | 2 +- config/consensus_test.go | 2 +- config/defaultsGenerator/defaultsGenerator.go | 2 +- config/dnsbootstrap.go | 2 +- config/dnsbootstrap_test.go | 2 +- config/keyfile.go | 2 +- config/localTemplate.go | 2 +- config/local_defaults.go | 2 +- config/migrate.go | 2 +- config/version.go | 2 +- config/version_test.go | 2 +- crypto/batchverifier.go | 2 +- crypto/batchverifier_test.go | 2 +- crypto/crypto_test.go | 2 +- crypto/cryptoerror.go | 2 +- crypto/curve25519.go | 2 +- crypto/curve25519_test.go | 2 +- crypto/digest.go | 2 +- crypto/encoding_test.go | 2 +- crypto/falconWrapper.go | 2 +- crypto/falconWrapper_test.go | 2 +- crypto/hashes.go | 2 +- crypto/hashes_test.go | 2 +- crypto/memcpy_chk_windows.c | 2 +- crypto/merklearray/array.go | 2 +- crypto/merklearray/layer.go | 2 +- crypto/merklearray/merkle.go | 2 +- crypto/merklearray/merkle_test.go | 2 +- crypto/merklearray/partial.go | 2 +- crypto/merklearray/proof.go | 2 +- crypto/merklearray/proof_test.go | 2 +- crypto/merklearray/vectorCommitmentArray.go | 2 +- crypto/merklearray/vectorCommitmentArray_test.go | 2 +- crypto/merklearray/worker.go | 2 +- crypto/merklesignature/committablePublicKeys.go | 2 +- crypto/merklesignature/committablePublicKeys_test.go | 2 +- crypto/merklesignature/const.go | 2 +- crypto/merklesignature/kats_test.go | 2 +- crypto/merklesignature/keysBuilder.go | 2 +- crypto/merklesignature/keysBuilder_test.go | 2 +- crypto/merklesignature/merkleSignatureScheme.go | 2 +- crypto/merklesignature/merkleSignatureScheme_test.go | 2 +- crypto/merklesignature/persistentMerkleSignatureScheme.go | 2 +- crypto/merklesignature/persistentMerkleSignatureScheme_test.go | 2 +- crypto/merklesignature/posdivs.go | 2 +- crypto/merklesignature/posdivs_test.go | 2 +- crypto/merkletrie/bitset.go | 2 +- crypto/merkletrie/bitset_test.go | 2 +- crypto/merkletrie/cache.go | 2 +- crypto/merkletrie/cache_test.go | 2 +- crypto/merkletrie/committer.go | 2 +- crypto/merkletrie/committer_test.go | 2 +- crypto/merkletrie/node.go | 2 +- crypto/merkletrie/node_test.go | 2 +- crypto/merkletrie/trie.go | 2 +- crypto/merkletrie/trie_test.go | 2 +- crypto/multisig.go | 2 +- crypto/multisig_test.go | 2 +- crypto/onetimesig.go | 2 +- crypto/onetimesig_test.go | 2 +- crypto/passphrase/errors.go | 2 +- crypto/passphrase/passphrase.go | 2 +- crypto/passphrase/passphrase_test.go | 2 +- crypto/passphrase/wordlist.go | 2 +- crypto/rand.go | 2 +- crypto/rand_test.go | 2 +- crypto/stateproof/coinGenerator.go | 2 +- crypto/stateproof/coinGenerator_test.go | 2 +- crypto/stateproof/committableSignatureSlot.go | 2 +- crypto/stateproof/committableSignatureSlot_test.go | 2 +- crypto/stateproof/const.go | 2 +- crypto/stateproof/prover.go | 2 +- crypto/stateproof/prover_test.go | 2 +- crypto/stateproof/structs.go | 2 +- crypto/stateproof/verifier.go | 2 +- crypto/stateproof/verifier_test.go | 2 +- crypto/stateproof/weights.go | 2 +- crypto/stateproof/weights_test.go | 2 +- crypto/statetrie/nibbles/nibbles.go | 2 +- crypto/statetrie/nibbles/nibbles_test.go | 2 +- crypto/util.go | 2 +- crypto/util_test.go | 2 +- crypto/vrf.go | 2 +- crypto/vrf_test.go | 2 +- daemon/algod/api/client/encoding.go | 2 +- daemon/algod/api/client/restClient.go | 2 +- daemon/algod/api/server/common/handlers.go | 2 +- daemon/algod/api/server/common/metrics.go | 2 +- daemon/algod/api/server/common/responses.go | 2 +- daemon/algod/api/server/common/routes.go | 2 +- daemon/algod/api/server/common/test/handlers_test.go | 2 +- daemon/algod/api/server/common/test/helpers.go | 2 +- daemon/algod/api/server/lib/common.go | 2 +- daemon/algod/api/server/lib/middlewares/auth.go | 2 +- daemon/algod/api/server/lib/middlewares/auth_test.go | 2 +- daemon/algod/api/server/lib/middlewares/connectionLimiter.go | 2 +- .../algod/api/server/lib/middlewares/connectionLimiter_test.go | 2 +- daemon/algod/api/server/lib/middlewares/cors.go | 2 +- daemon/algod/api/server/lib/middlewares/logger.go | 2 +- daemon/algod/api/server/router.go | 2 +- daemon/algod/api/server/router_test.go | 2 +- daemon/algod/api/server/v1/handlers/errors.go | 2 +- daemon/algod/api/server/v1/handlers/handlers.go | 2 +- daemon/algod/api/server/v1/routes/routes.go | 2 +- daemon/algod/api/server/v2/account.go | 2 +- daemon/algod/api/server/v2/account_test.go | 2 +- daemon/algod/api/server/v2/dryrun.go | 2 +- daemon/algod/api/server/v2/dryrun_test.go | 2 +- daemon/algod/api/server/v2/errors.go | 2 +- daemon/algod/api/server/v2/handlers.go | 2 +- daemon/algod/api/server/v2/handlers_test.go | 2 +- daemon/algod/api/server/v2/test/handlers_resources_test.go | 2 +- daemon/algod/api/server/v2/test/handlers_test.go | 2 +- daemon/algod/api/server/v2/test/helpers.go | 2 +- daemon/algod/api/server/v2/utils.go | 2 +- daemon/algod/api/spec/common/model.go | 2 +- daemon/algod/api/spec/v2/model.go | 2 +- daemon/algod/api/swagger.go | 2 +- daemon/algod/deadlockLogger.go | 2 +- daemon/algod/deadlock_test.go | 2 +- daemon/algod/server.go | 2 +- daemon/algod/server_test.go | 2 +- daemon/kmd/api/api.go | 2 +- daemon/kmd/api/cors.go | 2 +- daemon/kmd/api/v1/auth.go | 2 +- daemon/kmd/api/v1/errors.go | 2 +- daemon/kmd/api/v1/handlers.go | 2 +- daemon/kmd/client/client.go | 2 +- daemon/kmd/client/requests.go | 2 +- daemon/kmd/client/wrappers.go | 2 +- daemon/kmd/config/config.go | 2 +- daemon/kmd/config/errors.go | 2 +- daemon/kmd/kmd.go | 2 +- daemon/kmd/lib/kmdapi/common.go | 2 +- daemon/kmd/lib/kmdapi/requests.go | 2 +- daemon/kmd/lib/kmdapi/responses.go | 2 +- daemon/kmd/server/errors.go | 2 +- daemon/kmd/server/server.go | 2 +- daemon/kmd/session/auth.go | 2 +- daemon/kmd/session/session.go | 2 +- daemon/kmd/wallet/driver/driver.go | 2 +- daemon/kmd/wallet/driver/ledger.go | 2 +- daemon/kmd/wallet/driver/ledger_errors.go | 2 +- daemon/kmd/wallet/driver/ledger_hid.go | 2 +- daemon/kmd/wallet/driver/sqlite.go | 2 +- daemon/kmd/wallet/driver/sqlite_crypto.go | 2 +- daemon/kmd/wallet/driver/sqlite_errors.go | 2 +- daemon/kmd/wallet/driver/util.go | 2 +- daemon/kmd/wallet/wallet.go | 2 +- data/account/account.go | 2 +- data/account/partInstall.go | 2 +- data/account/participation.go | 2 +- data/account/participationRegistry.go | 2 +- data/account/participationRegistryBench_test.go | 2 +- data/account/participationRegistry_test.go | 2 +- data/account/participation_test.go | 2 +- data/account/registeryDbOps.go | 2 +- data/account/rootInstall.go | 2 +- data/accountManager.go | 2 +- data/accountManager_test.go | 2 +- data/appRateLimiter.go | 2 +- data/appRateLimiter_test.go | 2 +- data/basics/address.go | 2 +- data/basics/address_test.go | 2 +- data/basics/fields_test.go | 2 +- data/basics/overflow.go | 2 +- data/basics/sort.go | 2 +- data/basics/stateProofParticipant.go | 2 +- data/basics/teal.go | 2 +- data/basics/teal_test.go | 2 +- data/basics/testing/userBalance.go | 2 +- data/basics/units.go | 2 +- data/basics/units_test.go | 2 +- data/basics/userBalance.go | 2 +- data/basics/userBalance_test.go | 2 +- data/bookkeeping/block.go | 2 +- data/bookkeeping/block_test.go | 2 +- data/bookkeeping/encoding_test.go | 2 +- data/bookkeeping/genesis.go | 2 +- data/bookkeeping/genesis_test.go | 2 +- data/bookkeeping/lightBlockHeader.go | 2 +- data/bookkeeping/lightBlockHeader_test.go | 2 +- data/bookkeeping/prettyprinting.go | 2 +- data/bookkeeping/txn_merkle.go | 2 +- data/bookkeeping/txn_merkle_test.go | 2 +- data/committee/committee.go | 2 +- data/committee/common_test.go | 2 +- data/committee/credential.go | 2 +- data/committee/credential_test.go | 2 +- data/committee/encoding_test.go | 2 +- data/common_test.go | 2 +- data/datatest/fabricateLedger.go | 2 +- data/datatest/impls.go | 2 +- data/hashable/message.go | 2 +- data/ledger.go | 2 +- data/ledger_test.go | 2 +- data/pools/errors.go | 2 +- data/pools/statusCache.go | 2 +- data/pools/transactionPool.go | 2 +- data/pools/transactionPool_test.go | 2 +- data/stateproofmsg/message.go | 2 +- data/transactions/application.go | 2 +- data/transactions/application_test.go | 2 +- data/transactions/asset.go | 2 +- data/transactions/common_test.go | 2 +- data/transactions/error.go | 2 +- data/transactions/json_test.go | 2 +- data/transactions/keyreg.go | 2 +- data/transactions/logic/assembler.go | 2 +- data/transactions/logic/assembler_test.go | 2 +- data/transactions/logic/backwardCompat_test.go | 2 +- data/transactions/logic/blackbox_test.go | 2 +- data/transactions/logic/box.go | 2 +- data/transactions/logic/box_test.go | 2 +- data/transactions/logic/crypto.go | 2 +- data/transactions/logic/crypto_test.go | 2 +- data/transactions/logic/debugger.go | 2 +- data/transactions/logic/debugger_eval_test.go | 2 +- data/transactions/logic/debugger_test.go | 2 +- data/transactions/logic/doc.go | 2 +- data/transactions/logic/doc_test.go | 2 +- data/transactions/logic/eval.go | 2 +- data/transactions/logic/evalAppTxn_test.go | 2 +- data/transactions/logic/evalBench_test.go | 2 +- data/transactions/logic/evalStateful_test.go | 2 +- data/transactions/logic/eval_test.go | 2 +- data/transactions/logic/export_test.go | 2 +- data/transactions/logic/fields.go | 2 +- data/transactions/logic/fields_test.go | 2 +- data/transactions/logic/frames.go | 2 +- data/transactions/logic/frames_test.go | 2 +- data/transactions/logic/jsonspec_test.go | 2 +- data/transactions/logic/ledger_test.go | 2 +- data/transactions/logic/mocktracer/scenarios.go | 2 +- data/transactions/logic/mocktracer/tracer.go | 2 +- data/transactions/logic/opcodeExplain.go | 2 +- data/transactions/logic/opcodes.go | 2 +- data/transactions/logic/opcodes_test.go | 2 +- data/transactions/logic/pairing.go | 2 +- data/transactions/logic/pairing_test.go | 2 +- data/transactions/logic/program.go | 2 +- data/transactions/logic/resources.go | 2 +- data/transactions/logic/resources_test.go | 2 +- data/transactions/logic/sourcemap.go | 2 +- data/transactions/logic/sourcemap_test.go | 2 +- data/transactions/logic/tracer.go | 2 +- data/transactions/logic/tracer_test.go | 2 +- data/transactions/logicsig.go | 2 +- data/transactions/payment.go | 2 +- data/transactions/payment_test.go | 2 +- data/transactions/payset.go | 2 +- data/transactions/payset_test.go | 2 +- data/transactions/perf_test.go | 2 +- data/transactions/signedtxn.go | 2 +- data/transactions/signedtxn_test.go | 2 +- data/transactions/sort.go | 2 +- data/transactions/stateproof.go | 2 +- data/transactions/teal.go | 2 +- data/transactions/teal_test.go | 2 +- data/transactions/testhelpers.go | 2 +- data/transactions/transaction.go | 2 +- data/transactions/transaction_test.go | 2 +- data/transactions/verify/artifact_test.go | 2 +- data/transactions/verify/txn.go | 2 +- data/transactions/verify/txnBatch.go | 2 +- data/transactions/verify/txnBatch_test.go | 2 +- data/transactions/verify/txn_test.go | 2 +- data/transactions/verify/verifiedTxnCache.go | 2 +- data/transactions/verify/verifiedTxnCache_test.go | 2 +- data/txDupCache.go | 2 +- data/txDupCache_test.go | 2 +- data/txHandler.go | 2 +- data/txHandler_test.go | 2 +- data/txntest/defi.go | 2 +- data/txntest/txn.go | 2 +- gen/generate.go | 2 +- gen/generate_test.go | 2 +- gen/walletData.go | 2 +- internal/rapidgen/rapidgenerators.go | 2 +- internal/rapidgen/tld.go | 2 +- ledger/acctdeltas.go | 2 +- ledger/acctdeltas_test.go | 2 +- ledger/acctonline.go | 2 +- ledger/acctonline_expired_test.go | 2 +- ledger/acctonline_test.go | 2 +- ledger/acctupdates.go | 2 +- ledger/acctupdates_test.go | 2 +- ledger/applications_test.go | 2 +- ledger/apply/application.go | 2 +- ledger/apply/application_test.go | 2 +- ledger/apply/apply.go | 2 +- ledger/apply/asset.go | 2 +- ledger/apply/asset_test.go | 2 +- ledger/apply/keyreg.go | 2 +- ledger/apply/keyreg_test.go | 2 +- ledger/apply/mockBalances_test.go | 2 +- ledger/apply/payment.go | 2 +- ledger/apply/payment_test.go | 2 +- ledger/apply/stateproof.go | 2 +- ledger/apply/stateproof_test.go | 2 +- ledger/apptxn_test.go | 2 +- ledger/archival_test.go | 2 +- ledger/blockqueue.go | 2 +- ledger/blockqueue_test.go | 2 +- ledger/boxtxn_test.go | 2 +- ledger/bulletin.go | 2 +- ledger/bulletin_test.go | 2 +- ledger/catchpointfileheader.go | 2 +- ledger/catchpointfilewriter.go | 2 +- ledger/catchpointfilewriter_test.go | 2 +- ledger/catchpointtracker.go | 2 +- ledger/catchpointtracker_test.go | 2 +- ledger/catchupaccessor.go | 2 +- ledger/catchupaccessor_test.go | 2 +- ledger/double_test.go | 2 +- ledger/encoded/recordsV5.go | 2 +- ledger/encoded/recordsV6.go | 2 +- ledger/encoded/recordsV6_test.go | 2 +- ledger/eval/appcow.go | 2 +- ledger/eval/appcow_test.go | 2 +- ledger/eval/applications.go | 2 +- ledger/eval/assetcow.go | 2 +- ledger/eval/cow.go | 2 +- ledger/eval/cow_creatables.go | 2 +- ledger/eval/cow_test.go | 2 +- ledger/eval/eval.go | 2 +- ledger/eval/eval_test.go | 2 +- ledger/eval/evalindexer.go | 2 +- ledger/eval/prefetcher/error.go | 2 +- ledger/eval/prefetcher/prefetcher.go | 2 +- ledger/eval/prefetcher/prefetcher_alignment_test.go | 2 +- ledger/eval/prefetcher/prefetcher_test.go | 2 +- ledger/eval/prefetcher/prefetcher_whitebox_test.go | 2 +- ledger/eval/txntracer.go | 2 +- ledger/eval/txntracer_test.go | 2 +- ledger/eval_simple_test.go | 2 +- ledger/evalbench_test.go | 2 +- ledger/evalindexer.go | 2 +- ledger/evalindexer_test.go | 2 +- ledger/fullblock_perf_test.go | 2 +- ledger/ledger.go | 2 +- ledger/ledger_perf_test.go | 2 +- ledger/ledger_test.go | 2 +- ledger/ledgercore/accountdata.go | 2 +- ledger/ledgercore/accountdata_test.go | 2 +- ledger/ledgercore/accountresource.go | 2 +- ledger/ledgercore/catchpointlabel.go | 2 +- ledger/ledgercore/catchpointlabel_test.go | 2 +- ledger/ledgercore/error.go | 2 +- ledger/ledgercore/misc.go | 2 +- ledger/ledgercore/onlineacct.go | 2 +- ledger/ledgercore/statedelta.go | 2 +- ledger/ledgercore/statedelta_test.go | 2 +- ledger/ledgercore/stateproofverification.go | 2 +- ledger/ledgercore/totals.go | 2 +- ledger/ledgercore/totals_test.go | 2 +- ledger/ledgercore/validatedBlock.go | 2 +- ledger/ledgercore/votersForRound.go | 2 +- ledger/lruaccts.go | 2 +- ledger/lruaccts_test.go | 2 +- ledger/lrukv.go | 2 +- ledger/lrukv_test.go | 2 +- ledger/lruonlineaccts.go | 2 +- ledger/lruonlineaccts_test.go | 2 +- ledger/lruresources.go | 2 +- ledger/lruresources_test.go | 2 +- ledger/metrics.go | 2 +- ledger/metrics_test.go | 2 +- ledger/notifier.go | 2 +- ledger/onlineaccountscache.go | 2 +- ledger/onlineaccountscache_test.go | 2 +- ledger/onlinetopheap.go | 2 +- ledger/onlinetopheap_test.go | 2 +- ledger/perf_test.go | 2 +- ledger/simple_test.go | 2 +- ledger/simulation/initialStates.go | 2 +- ledger/simulation/resources.go | 2 +- ledger/simulation/resources_test.go | 2 +- ledger/simulation/simulation_eval_test.go | 2 +- ledger/simulation/simulator.go | 2 +- ledger/simulation/simulator_test.go | 2 +- ledger/simulation/testing/utils.go | 2 +- ledger/simulation/trace.go | 2 +- ledger/simulation/tracer.go | 2 +- ledger/simulation/tracer_test.go | 2 +- ledger/spverificationtracker.go | 2 +- ledger/spverificationtracker_test.go | 2 +- ledger/store/blockdb/blockdb.go | 2 +- ledger/store/blockdb/blockdb_test.go | 2 +- ledger/store/testing/helpers.go | 2 +- ledger/store/trackerdb/catchpoint.go | 2 +- ledger/store/trackerdb/data.go | 2 +- ledger/store/trackerdb/data_test.go | 2 +- ledger/store/trackerdb/dualdriver/accounts_reader.go | 2 +- ledger/store/trackerdb/dualdriver/accounts_reader_ext.go | 2 +- ledger/store/trackerdb/dualdriver/accounts_writer.go | 2 +- ledger/store/trackerdb/dualdriver/accounts_writer_ext.go | 2 +- ledger/store/trackerdb/dualdriver/dualdriver.go | 2 +- ledger/store/trackerdb/dualdriver/online_accounts_reader.go | 2 +- ledger/store/trackerdb/dualdriver/online_accounts_writer.go | 2 +- ledger/store/trackerdb/dualdriver/stateproof_reader.go | 2 +- ledger/store/trackerdb/dualdriver/stateproof_writer.go | 2 +- ledger/store/trackerdb/dualdriver/transaction_for_testing.go | 2 +- ledger/store/trackerdb/generickv/accounts_ext_reader.go | 2 +- ledger/store/trackerdb/generickv/accounts_ext_writer.go | 2 +- ledger/store/trackerdb/generickv/accounts_reader.go | 2 +- ledger/store/trackerdb/generickv/accounts_writer.go | 2 +- ledger/store/trackerdb/generickv/catchpoint.go | 2 +- ledger/store/trackerdb/generickv/init_accounts.go | 2 +- ledger/store/trackerdb/generickv/migrations.go | 2 +- ledger/store/trackerdb/generickv/onlineaccounts_reader.go | 2 +- ledger/store/trackerdb/generickv/onlineaccounts_writer.go | 2 +- ledger/store/trackerdb/generickv/reader.go | 2 +- ledger/store/trackerdb/generickv/schema.go | 2 +- ledger/store/trackerdb/generickv/stateproof_reader.go | 2 +- ledger/store/trackerdb/generickv/stateproof_writer.go | 2 +- ledger/store/trackerdb/generickv/writer.go | 2 +- ledger/store/trackerdb/hashing.go | 2 +- ledger/store/trackerdb/interface.go | 2 +- ledger/store/trackerdb/params.go | 2 +- ledger/store/trackerdb/pebbledbdriver/pebbledriver.go | 2 +- ledger/store/trackerdb/pebbledbdriver/pebbledriver_arm.go | 2 +- ledger/store/trackerdb/pebbledbdriver/testing.go | 2 +- ledger/store/trackerdb/sqlitedriver/accountsV2.go | 2 +- ledger/store/trackerdb/sqlitedriver/accountsV2_test.go | 2 +- ledger/store/trackerdb/sqlitedriver/catchpoint.go | 2 +- .../store/trackerdb/sqlitedriver/catchpointPendingHashesIter.go | 2 +- ledger/store/trackerdb/sqlitedriver/catchpoint_test.go | 2 +- ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go | 2 +- ledger/store/trackerdb/sqlitedriver/kvsIter.go | 2 +- ledger/store/trackerdb/sqlitedriver/merkle_committer.go | 2 +- ledger/store/trackerdb/sqlitedriver/orderedAccountsIter.go | 2 +- ledger/store/trackerdb/sqlitedriver/schema.go | 2 +- ledger/store/trackerdb/sqlitedriver/schema_test.go | 2 +- ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go | 2 +- ledger/store/trackerdb/sqlitedriver/sql.go | 2 +- ledger/store/trackerdb/sqlitedriver/sql_test.go | 2 +- ledger/store/trackerdb/sqlitedriver/sqlitedriver.go | 2 +- ledger/store/trackerdb/sqlitedriver/testing.go | 2 +- ledger/store/trackerdb/sqlitedriver/trackerdbV2.go | 2 +- ledger/store/trackerdb/store.go | 2 +- ledger/store/trackerdb/testdb/testdb.go | 2 +- ledger/store/trackerdb/testinterface.go | 2 +- ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go | 2 +- ledger/store/trackerdb/testsuite/accounts_kv_test.go | 2 +- ledger/store/trackerdb/testsuite/dbsemantics_test.go | 2 +- ledger/store/trackerdb/testsuite/dual_test.go | 2 +- ledger/store/trackerdb/testsuite/migration_test.go | 2 +- ledger/store/trackerdb/testsuite/mockdb_test.go | 2 +- ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go | 2 +- ledger/store/trackerdb/testsuite/pebbledb_test.go | 2 +- ledger/store/trackerdb/testsuite/sqlitedb_test.go | 2 +- ledger/store/trackerdb/testsuite/stateproofs_kv_test.go | 2 +- ledger/store/trackerdb/testsuite/utils_test.go | 2 +- ledger/store/trackerdb/utils.go | 2 +- ledger/store/trackerdb/version.go | 2 +- ledger/testing/accountsTotals.go | 2 +- ledger/testing/consensusRange.go | 2 +- ledger/testing/consensusRange_test.go | 2 +- ledger/testing/initState.go | 2 +- ledger/testing/randomAccounts.go | 2 +- ledger/testing/randomAccounts_test.go | 2 +- ledger/testing/testGenesis.go | 2 +- ledger/testing/withAndWithoutCache.go | 2 +- ledger/tracker.go | 2 +- ledger/tracker_test.go | 2 +- ledger/trackerdb.go | 2 +- ledger/txnbench_test.go | 2 +- ledger/txtail.go | 2 +- ledger/txtail_test.go | 2 +- ledger/voters.go | 2 +- ledger/voters_test.go | 2 +- libgoal/accounts.go | 2 +- libgoal/error.go | 2 +- libgoal/libgoal.go | 2 +- libgoal/libgoal_test.go | 2 +- libgoal/lockedFile.go | 2 +- libgoal/lockedFileLinux.go | 2 +- libgoal/lockedFileUnix.go | 2 +- libgoal/lockedFileWindows.go | 2 +- libgoal/participation.go | 2 +- libgoal/participation/participation.go | 2 +- libgoal/participation/participation_test.go | 2 +- libgoal/system.go | 2 +- libgoal/teal.go | 2 +- libgoal/transactions.go | 2 +- libgoal/unencryptedWallet.go | 2 +- libgoal/walletHandles.go | 2 +- libgoal/wallets.go | 2 +- logging/collector.go | 2 +- logging/cyclicWriter.go | 2 +- logging/cyclicWriter_test.go | 2 +- logging/logBuffer.go | 2 +- logging/logBuffer_test.go | 2 +- logging/log_test.go | 2 +- logging/logspec/agreement.go | 2 +- logging/logspec/ledger.go | 2 +- logging/logspec/root.go | 2 +- logging/telemetry.go | 2 +- logging/telemetryCommon.go | 2 +- logging/telemetryConfig.go | 2 +- logging/telemetryConfig_test.go | 2 +- logging/telemetryFilteredHook.go | 2 +- logging/telemetry_test.go | 2 +- logging/telemetryhook.go | 2 +- logging/telemetryhook_test.go | 2 +- logging/telemetryspec/category.go | 2 +- logging/telemetryspec/event.go | 2 +- logging/telemetryspec/eventTiming.go | 2 +- logging/telemetryspec/metric.go | 2 +- logging/telemetryspec/metric_test.go | 2 +- logging/testingLogger.go | 2 +- logging/usage.go | 2 +- netdeploy/network.go | 2 +- netdeploy/networkTemplate.go | 2 +- netdeploy/networkTemplates_test.go | 2 +- netdeploy/network_test.go | 2 +- netdeploy/remote/bootstrappedNetwork.go | 2 +- netdeploy/remote/bootstrappedNetwork_test.go | 2 +- netdeploy/remote/buildConfig.go | 2 +- netdeploy/remote/deployedNetwork.go | 2 +- netdeploy/remote/deployedNetwork_test.go | 2 +- netdeploy/remote/hostConfig.go | 2 +- netdeploy/remote/hostTemplate.go | 2 +- netdeploy/remote/nodeConfig.go | 2 +- netdeploy/remote/nodeWalletData.go | 2 +- netdeploy/remote/nodecfg/nodeConfigurator.go | 2 +- netdeploy/remote/nodecfg/nodeDir.go | 2 +- netdeploy/remote/topology.go | 2 +- network/addr.go | 2 +- network/addr_test.go | 2 +- network/connPerfMon.go | 2 +- network/connPerfMon_test.go | 2 +- network/dialer.go | 2 +- network/gossipNode.go | 2 +- network/limited_reader_slurper.go | 2 +- network/limited_reader_slurper_test.go | 2 +- network/messageFilter.go | 2 +- network/messageFilter_test.go | 2 +- network/messagetracer/graphtrace.go | 2 +- network/messagetracer/interface.go | 2 +- network/msgCompressor.go | 2 +- network/msgCompressor_test.go | 2 +- network/msgOfInterest.go | 2 +- network/msgOfInterest_test.go | 2 +- network/multiplexer.go | 2 +- network/multiplexer_test.go | 2 +- network/netidentity.go | 2 +- network/netidentity_test.go | 2 +- network/netprio.go | 2 +- network/netprio_test.go | 2 +- network/p2p/dnsaddr/resolve.go | 2 +- network/p2p/dnsaddr/resolveController.go | 2 +- network/p2p/dnsaddr/resolveController_test.go | 2 +- network/p2p/dnsaddr/resolve_test.go | 2 +- network/p2p/p2p.go | 2 +- network/p2p/p2p_test.go | 2 +- network/p2p/peerID.go | 2 +- network/p2p/peerID_test.go | 2 +- network/p2p/peerstore/peerstore.go | 2 +- network/p2p/peerstore/peerstore_test.go | 2 +- network/p2p/peerstore/utils.go | 2 +- network/p2p/peerstore/utils_test.go | 2 +- network/p2p/pubsub.go | 2 +- network/p2p/streams.go | 2 +- network/p2pNetwork.go | 2 +- network/p2pNetwork_test.go | 2 +- network/p2pPeer.go | 2 +- network/peersheap.go | 2 +- network/phonebook.go | 2 +- network/phonebook_test.go | 2 +- network/rateLimitingTransport.go | 2 +- network/requestLogger.go | 2 +- network/requestLogger_test.go | 2 +- network/requestTracker.go | 2 +- network/requestTracker_test.go | 2 +- network/topics.go | 2 +- network/topics_test.go | 2 +- network/websocketProxy_test.go | 2 +- network/wsNetwork.go | 2 +- network/wsNetwork_test.go | 2 +- network/wsPeer.go | 2 +- network/wsPeer_test.go | 2 +- node/assemble_test.go | 2 +- node/error.go | 2 +- node/follower_node.go | 2 +- node/follower_node_test.go | 2 +- node/impls.go | 2 +- node/netprio.go | 2 +- node/netprio_test.go | 2 +- node/node.go | 2 +- node/node_test.go | 2 +- nodecontrol/LaggedStdIo.go | 2 +- nodecontrol/NodeController.go | 2 +- nodecontrol/algodControl.go | 2 +- nodecontrol/algodControl_test.go | 2 +- nodecontrol/kmdControl.go | 2 +- nodecontrol/kmdControl_common.go | 2 +- nodecontrol/kmdControl_windows.go | 2 +- nodecontrol/nodeControlErrors.go | 2 +- protocol/codec.go | 2 +- protocol/codec_test.go | 2 +- protocol/codec_tester.go | 2 +- protocol/consensus.go | 2 +- protocol/encodebench_test.go | 2 +- protocol/hash.go | 2 +- protocol/hash_test.go | 2 +- protocol/networks.go | 2 +- protocol/stateproof.go | 2 +- protocol/tags.go | 2 +- protocol/tags_test.go | 2 +- protocol/test/allocbound_slice.go | 2 +- protocol/transcode/core.go | 2 +- protocol/transcode/core_test.go | 2 +- protocol/txntype.go | 2 +- rpcs/blockService.go | 2 +- rpcs/blockService_test.go | 2 +- rpcs/httpTxSync.go | 2 +- rpcs/ledgerService.go | 2 +- rpcs/ledgerService_test.go | 2 +- rpcs/registrar.go | 2 +- rpcs/txService.go | 2 +- rpcs/txService_test.go | 2 +- rpcs/txSyncer.go | 2 +- rpcs/txSyncer_test.go | 2 +- shared/algoh/config.go | 2 +- shared/pingpong/accounts.go | 2 +- shared/pingpong/accounts_test.go | 2 +- shared/pingpong/config.go | 2 +- shared/pingpong/pingpong.go | 2 +- stateproof/abstractions.go | 2 +- stateproof/builder.go | 2 +- stateproof/db.go | 2 +- stateproof/db_test.go | 2 +- stateproof/recovery.go | 2 +- stateproof/signer.go | 2 +- stateproof/stateproofMessageGenerator.go | 2 +- stateproof/stateproofMessageGenerator_test.go | 2 +- stateproof/verify/stateproof.go | 2 +- stateproof/verify/stateproof_test.go | 2 +- stateproof/worker.go | 2 +- stateproof/worker_test.go | 2 +- test/commandandcontrol/cc_agent/component/agent.go | 2 +- test/commandandcontrol/cc_agent/component/agent_test.go | 2 +- test/commandandcontrol/cc_agent/component/pingPongComponent.go | 2 +- test/commandandcontrol/cc_agent/main.go | 2 +- test/commandandcontrol/cc_client/main.go | 2 +- test/commandandcontrol/cc_service/main.go | 2 +- test/commandandcontrol/lib/ccCommon.go | 2 +- test/e2e-go/cli/algod/cleanup_test.go | 2 +- test/e2e-go/cli/algod/expect/algod_expect_test.go | 2 +- test/e2e-go/cli/algod/stdstreams_test.go | 2 +- test/e2e-go/cli/algoh/expect/algoh_expect_test.go | 2 +- test/e2e-go/cli/goal/account_test.go | 2 +- test/e2e-go/cli/goal/clerk_test.go | 2 +- test/e2e-go/cli/goal/common_test.go | 2 +- .../cli/goal/expect/catchpointCatchupWebProxy/webproxy.go | 2 +- test/e2e-go/cli/goal/expect/goal_expect_test.go | 2 +- test/e2e-go/cli/goal/node_cleanup_test.go | 2 +- test/e2e-go/cli/perf/libgoal_test.go | 2 +- test/e2e-go/cli/perf/payment_test.go | 2 +- test/e2e-go/cli/tealdbg/cdtmock/main.go | 2 +- test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go | 2 +- test/e2e-go/features/accountPerf/sixMillion_test.go | 2 +- test/e2e-go/features/catchup/basicCatchup_test.go | 2 +- test/e2e-go/features/catchup/catchpointCatchup_test.go | 2 +- test/e2e-go/features/catchup/stateproofsCatchup_test.go | 2 +- test/e2e-go/features/devmode/devmode_test.go | 2 +- test/e2e-go/features/followernode/syncDeltas_test.go | 2 +- test/e2e-go/features/followernode/syncRestart_test.go | 2 +- test/e2e-go/features/multisig/multisig_test.go | 2 +- test/e2e-go/features/p2p/p2p_basic_test.go | 2 +- .../participation/accountParticipationTransitions_test.go | 2 +- test/e2e-go/features/participation/deletePartKeys_test.go | 2 +- .../features/participation/onlineOfflineParticipation_test.go | 2 +- .../features/participation/overlappingParticipationKeys_test.go | 2 +- .../features/participation/participationExpiration_test.go | 2 +- test/e2e-go/features/participation/participationRewards_test.go | 2 +- .../e2e-go/features/partitionRecovery/partitionRecovery_test.go | 2 +- test/e2e-go/features/privatenet/privatenet_test.go | 2 +- test/e2e-go/features/stateproofs/stateproofs_test.go | 2 +- test/e2e-go/features/teal/compile_test.go | 2 +- test/e2e-go/features/transactions/accountv2_test.go | 2 +- test/e2e-go/features/transactions/app_pages_test.go | 2 +- test/e2e-go/features/transactions/application_test.go | 2 +- test/e2e-go/features/transactions/asset_test.go | 2 +- test/e2e-go/features/transactions/close_account_test.go | 2 +- test/e2e-go/features/transactions/group_test.go | 2 +- test/e2e-go/features/transactions/lease_test.go | 2 +- test/e2e-go/features/transactions/onlineStatusChange_test.go | 2 +- test/e2e-go/features/transactions/proof_test.go | 2 +- test/e2e-go/features/transactions/sendReceive_test.go | 2 +- test/e2e-go/globals/constants.go | 2 +- test/e2e-go/kmd/e2e_kmd_server_client_test.go | 2 +- test/e2e-go/kmd/e2e_kmd_sqlite_test.go | 2 +- test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go | 2 +- test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go | 2 +- test/e2e-go/kmd/e2e_kmd_wallet_test.go | 2 +- test/e2e-go/perf/basic_test.go | 2 +- test/e2e-go/restAPI/helpers.go | 2 +- test/e2e-go/restAPI/other/appsRestAPI_test.go | 2 +- test/e2e-go/restAPI/other/misc_test.go | 2 +- test/e2e-go/restAPI/restClient_test.go | 2 +- test/e2e-go/restAPI/simulate/simulateRestAPI_test.go | 2 +- test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go | 2 +- test/e2e-go/stress/transactions/createManyAndGoOnline_test.go | 2 +- test/e2e-go/upgrades/application_support_test.go | 2 +- test/e2e-go/upgrades/rekey_support_test.go | 2 +- test/e2e-go/upgrades/send_receive_upgrade_test.go | 2 +- test/e2e-go/upgrades/stateproof_participation_test.go | 2 +- test/framework/fixtures/baseFixture.go | 2 +- test/framework/fixtures/expectFixture.go | 2 +- test/framework/fixtures/fixture.go | 2 +- test/framework/fixtures/goalFixture.go | 2 +- test/framework/fixtures/kmdFixture.go | 2 +- test/framework/fixtures/libgoalFixture.go | 2 +- test/framework/fixtures/restClientFixture.go | 2 +- test/framework/fixtures/webProxyFixture.go | 2 +- test/heapwatch/block_history.py | 2 +- test/heapwatch/block_history_plot.py | 2 +- test/heapwatch/block_history_relays.py | 2 +- test/heapwatch/client_ram_report.py | 2 +- test/heapwatch/heapWatch.py | 2 +- test/heapwatch/metrics_delta.py | 2 +- test/heapwatch/nodeHostTarget.py | 2 +- test/heapwatch/runNodeHost.py | 2 +- test/linttest/lintissues.go | 2 +- test/netperf-go/puppeteer/main.go | 2 +- test/netperf-go/puppeteer/promMetricFetcher.go | 2 +- test/netperf-go/puppeteer/promMetricFetcher_test.go | 2 +- test/netperf-go/puppeteer/puppeteer.go | 2 +- test/netperf-go/puppeteer/puppeteer_test.go | 2 +- test/netperf-go/puppeteer/roundpoller.go | 2 +- test/partitiontest/filtering.go | 2 +- test/reflectionhelpers/helpers.go | 2 +- tools/block-generator/core/commands.go | 2 +- tools/block-generator/generator/config.go | 2 +- tools/block-generator/generator/config_test.go | 2 +- tools/block-generator/generator/daemon.go | 2 +- tools/block-generator/generator/generate.go | 2 +- tools/block-generator/generator/generate_apps.go | 2 +- tools/block-generator/generator/generate_test.go | 2 +- tools/block-generator/generator/generator_ledger.go | 2 +- tools/block-generator/generator/generator_types.go | 2 +- tools/block-generator/generator/make_transactions.go | 2 +- tools/block-generator/generator/server.go | 2 +- tools/block-generator/generator/server_test.go | 2 +- tools/block-generator/generator/utils.go | 2 +- tools/block-generator/generator/utils_test.go | 2 +- tools/block-generator/main.go | 2 +- tools/block-generator/runner/metrics_collector.go | 2 +- tools/block-generator/runner/reporting_test.go | 2 +- tools/block-generator/runner/run.go | 2 +- tools/block-generator/runner/runner.go | 2 +- tools/block-generator/util/util.go | 2 +- tools/boxkey/convertBoxKey.go | 2 +- tools/debug/algodump/main.go | 2 +- tools/debug/carpenter/main.go | 2 +- tools/debug/chopper/main.go | 2 +- tools/debug/coroner/main.go | 2 +- tools/debug/determaccount/main.go | 2 +- tools/debug/dumpblocks/main.go | 2 +- tools/debug/genconsensusconfig/main.go | 2 +- tools/debug/logfilter/main.go | 2 +- tools/debug/logfilter/main_test.go | 2 +- tools/debug/transplanter/main.go | 2 +- tools/misc/convertAddress.go | 2 +- tools/network/bootstrap.go | 2 +- tools/network/bootstrap_test.go | 2 +- tools/network/cloudflare/cloudflare.go | 2 +- tools/network/cloudflare/createRecord.go | 2 +- tools/network/cloudflare/deleteRecord.go | 2 +- tools/network/cloudflare/helpers.go | 2 +- tools/network/cloudflare/listRecords.go | 2 +- tools/network/cloudflare/updateRecord.go | 2 +- tools/network/cloudflare/zones.go | 2 +- tools/network/dnssec/anchor.go | 2 +- tools/network/dnssec/anchor_test.go | 2 +- tools/network/dnssec/client.go | 2 +- tools/network/dnssec/client_test.go | 2 +- tools/network/dnssec/config.go | 2 +- tools/network/dnssec/config_test.go | 2 +- tools/network/dnssec/config_unix.go | 2 +- tools/network/dnssec/config_unix_test.go | 2 +- tools/network/dnssec/config_windows.go | 2 +- tools/network/dnssec/dialer.go | 2 +- tools/network/dnssec/dnssec_test.go | 2 +- tools/network/dnssec/relay-check/main.go | 2 +- tools/network/dnssec/resolver.go | 2 +- tools/network/dnssec/sort.go | 2 +- tools/network/dnssec/sort_test.go | 2 +- tools/network/dnssec/testHarness.go | 2 +- tools/network/dnssec/trustchain.go | 2 +- tools/network/dnssec/trustedchain_test.go | 2 +- tools/network/dnssec/trustedzone.go | 2 +- tools/network/dnssec/trustedzone_test.go | 2 +- tools/network/dnssec/util.go | 2 +- tools/network/dnssec/util_test.go | 2 +- tools/network/externalIP.go | 2 +- tools/network/resolveController.go | 2 +- tools/network/resolveController_test.go | 2 +- tools/network/resolver.go | 2 +- tools/network/resolver_test.go | 2 +- tools/network/telemetryURIUpdateService.go | 2 +- tools/network/telemetryURIUpdateService_test.go | 2 +- tools/teal/algotmpl/extract.go | 2 +- tools/teal/algotmpl/main.go | 2 +- tools/teal/dkey/dsign/main.go | 2 +- tools/teal/tealcut/main.go | 2 +- tools/x-repo-types/typeAnalyzer/main.go | 2 +- tools/x-repo-types/typeAnalyzer/main.tmpl | 2 +- tools/x-repo-types/typeAnalyzer/typeAnalyzer.go | 2 +- tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go | 2 +- tools/x-repo-types/xrt.go | 2 +- tools/x-repo-types/xrt_test.go | 2 +- util/cmdUtils.go | 2 +- util/codecs/json.go | 2 +- util/codecs/json_test.go | 2 +- util/condvar/timedwait.go | 2 +- util/condvar/timedwait_test.go | 2 +- util/db/dbpair.go | 2 +- util/db/dbutil.go | 2 +- util/db/dbutil_test.go | 2 +- util/db/fullfsync_darwin.go | 2 +- util/db/initialize.go | 2 +- util/db/initialize_test.go | 2 +- util/db/interfaces.go | 2 +- util/db/perf_test.go | 2 +- util/db/versioning.go | 2 +- util/db/versioning_test.go | 2 +- util/execpool/backlog.go | 2 +- util/execpool/pool.go | 2 +- util/execpool/stream.go | 2 +- util/execpool/stream_test.go | 2 +- util/io.go | 2 +- util/io_test.go | 2 +- util/list.go | 2 +- util/list_test.go | 2 +- util/metrics/counter.go | 2 +- util/metrics/counterCommon.go | 2 +- util/metrics/counter_test.go | 2 +- util/metrics/gauge.go | 2 +- util/metrics/gauge_test.go | 2 +- util/metrics/metrics.go | 2 +- util/metrics/metrics_test.go | 2 +- util/metrics/registry.go | 2 +- util/metrics/registryCommon.go | 2 +- util/metrics/registry_test.go | 2 +- util/metrics/reporter.go | 2 +- util/metrics/reporter_test.go | 2 +- util/metrics/runtime.go | 2 +- util/metrics/runtime_test.go | 2 +- util/metrics/service.go | 2 +- util/metrics/serviceCommon.go | 2 +- util/metrics/tagcounter.go | 2 +- util/metrics/tagcounter_test.go | 2 +- util/process.go | 2 +- util/process_common.go | 2 +- util/process_windows.go | 2 +- util/rateLimit.go | 2 +- util/rateLimit_test.go | 2 +- util/s3/fileIterator.go | 2 +- util/s3/s3Helper.go | 2 +- util/s3/s3Helper_test.go | 2 +- util/set.go | 2 +- util/sleep.go | 2 +- util/sleep_linux.go | 2 +- util/sleep_linux_32.go | 2 +- util/sleep_linux_64.go | 2 +- util/tar/tar.go | 2 +- util/tar/untar.go | 2 +- util/tcpinfo.go | 2 +- util/tcpinfo_darwin.go | 2 +- util/tcpinfo_linux.go | 2 +- util/tcpinfo_noop.go | 2 +- util/timers/frozen.go | 2 +- util/timers/interface.go | 2 +- util/timers/monotonic.go | 2 +- util/timers/monotonic_test.go | 2 +- util/tokens/tokens.go | 2 +- util/util.go | 2 +- util/util_windows.go | 2 +- util/uuid/uuid.go | 2 +- util/uuid/uuid_test.go | 2 +- util/watchdogStreamReader.go | 2 +- 1154 files changed, 1154 insertions(+), 1154 deletions(-) diff --git a/README.md b/README.md index f1ebceceaa..6925f02225 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,6 @@ Provides utilities for the various components: Please see the [COPYING_FAQ](COPYING_FAQ) for details about how to apply our license. -Copyright (C) 2019-2023, Algorand Inc. +Copyright (C) 2019-2024, Algorand Inc. [developer site url]: https://developer.algorand.org/ diff --git a/agreement/abstractions.go b/agreement/abstractions.go index 44aafa4fd8..a22a3a0526 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/actions.go b/agreement/actions.go index ef2dd76c2b..9a6fa7138e 100644 --- a/agreement/actions.go +++ b/agreement/actions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/actor.go b/agreement/actor.go index fa5e0892a6..aeae4ce6bc 100644 --- a/agreement/actor.go +++ b/agreement/actor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/agreeInstall.go b/agreement/agreeInstall.go index 28b1985b22..e07134d720 100644 --- a/agreement/agreeInstall.go +++ b/agreement/agreeInstall.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/agreementtest/keyManager.go b/agreement/agreementtest/keyManager.go index 6222cffb6e..d17d726584 100644 --- a/agreement/agreementtest/keyManager.go +++ b/agreement/agreementtest/keyManager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/agreementtest/simulate.go b/agreement/agreementtest/simulate.go index 408b44a29a..da9c42bf0e 100644 --- a/agreement/agreementtest/simulate.go +++ b/agreement/agreementtest/simulate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go index 53c42411ed..a0afca4d46 100644 --- a/agreement/agreementtest/simulate_test.go +++ b/agreement/agreementtest/simulate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/asyncVoteVerifier.go b/agreement/asyncVoteVerifier.go index e30c9c5b1a..659f25663a 100644 --- a/agreement/asyncVoteVerifier.go +++ b/agreement/asyncVoteVerifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/asyncVoteVerifier_test.go b/agreement/asyncVoteVerifier_test.go index e344d10b70..5395740077 100644 --- a/agreement/asyncVoteVerifier_test.go +++ b/agreement/asyncVoteVerifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/autopsy.go b/agreement/autopsy.go index 30f41e13cb..82df84473a 100644 --- a/agreement/autopsy.go +++ b/agreement/autopsy.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/bundle.go b/agreement/bundle.go index a7670b53db..b84b86505c 100644 --- a/agreement/bundle.go +++ b/agreement/bundle.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/bundle_test.go b/agreement/bundle_test.go index 7bbb42f2e6..4038a9f48f 100644 --- a/agreement/bundle_test.go +++ b/agreement/bundle_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cadaver.go b/agreement/cadaver.go index 3b5c8a78cd..8437262202 100644 --- a/agreement/cadaver.go +++ b/agreement/cadaver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/certificate.go b/agreement/certificate.go index 6ce4b9e0a4..818b16488c 100644 --- a/agreement/certificate.go +++ b/agreement/certificate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/certificate_test.go b/agreement/certificate_test.go index 9ae2ebfa2e..04f2f6cef5 100644 --- a/agreement/certificate_test.go +++ b/agreement/certificate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/common_test.go b/agreement/common_test.go index 0c11d9553d..9ec85618e8 100644 --- a/agreement/common_test.go +++ b/agreement/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/coservice.go b/agreement/coservice.go index 7fe4ab1f52..7def929c15 100644 --- a/agreement/coservice.go +++ b/agreement/coservice.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/credentialArrivalHistory.go b/agreement/credentialArrivalHistory.go index b4bc48c976..7b302cd89e 100644 --- a/agreement/credentialArrivalHistory.go +++ b/agreement/credentialArrivalHistory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/credentialArrivalHistory_test.go b/agreement/credentialArrivalHistory_test.go index 8626094dd8..eb6cef639d 100644 --- a/agreement/credentialArrivalHistory_test.go +++ b/agreement/credentialArrivalHistory_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoRequestContext.go b/agreement/cryptoRequestContext.go index a9c512a517..cd779db38c 100644 --- a/agreement/cryptoRequestContext.go +++ b/agreement/cryptoRequestContext.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoRequestContext_test.go b/agreement/cryptoRequestContext_test.go index 44b279aeed..f5c5db8f78 100644 --- a/agreement/cryptoRequestContext_test.go +++ b/agreement/cryptoRequestContext_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoVerifier.go b/agreement/cryptoVerifier.go index 70609a634f..df7e9694cf 100644 --- a/agreement/cryptoVerifier.go +++ b/agreement/cryptoVerifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/cryptoVerifier_test.go b/agreement/cryptoVerifier_test.go index 880702b8da..5a7e19ccfa 100644 --- a/agreement/cryptoVerifier_test.go +++ b/agreement/cryptoVerifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/demux.go b/agreement/demux.go index 2653b8d8c1..33e15e6cfd 100644 --- a/agreement/demux.go +++ b/agreement/demux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/demux_test.go b/agreement/demux_test.go index c6aee6619c..e644ce76b3 100644 --- a/agreement/demux_test.go +++ b/agreement/demux_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/doc.go b/agreement/doc.go index fbf3ee433d..12a6bacd14 100644 --- a/agreement/doc.go +++ b/agreement/doc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/dynamicFilterTimeoutParams.go b/agreement/dynamicFilterTimeoutParams.go index 8b02bf082d..be4febd6ba 100644 --- a/agreement/dynamicFilterTimeoutParams.go +++ b/agreement/dynamicFilterTimeoutParams.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/dynamicFilterTimeoutParams_test.go b/agreement/dynamicFilterTimeoutParams_test.go index 7d03de7dea..1e84642faf 100644 --- a/agreement/dynamicFilterTimeoutParams_test.go +++ b/agreement/dynamicFilterTimeoutParams_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/encoding_test.go b/agreement/encoding_test.go index 7554ec4467..0f198cca46 100644 --- a/agreement/encoding_test.go +++ b/agreement/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/errors.go b/agreement/errors.go index e0929b63e4..9d4189056c 100644 --- a/agreement/errors.go +++ b/agreement/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/events.go b/agreement/events.go index afcfa3052d..f8d8eca475 100644 --- a/agreement/events.go +++ b/agreement/events.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/events_test.go b/agreement/events_test.go index c558f0067b..87a494d02d 100644 --- a/agreement/events_test.go +++ b/agreement/events_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/bandwidthFilter_test.go b/agreement/fuzzer/bandwidthFilter_test.go index 93b958f187..c527976ed2 100644 --- a/agreement/fuzzer/bandwidthFilter_test.go +++ b/agreement/fuzzer/bandwidthFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/catchupFilter_test.go b/agreement/fuzzer/catchupFilter_test.go index 2ef23d44b2..79089b5ea7 100644 --- a/agreement/fuzzer/catchupFilter_test.go +++ b/agreement/fuzzer/catchupFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/clockedFilter_test.go b/agreement/fuzzer/clockedFilter_test.go index ee4884acd4..9ff93be65c 100644 --- a/agreement/fuzzer/clockedFilter_test.go +++ b/agreement/fuzzer/clockedFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/dropMessageFilter_test.go b/agreement/fuzzer/dropMessageFilter_test.go index 976a1d4482..1151a5b541 100644 --- a/agreement/fuzzer/dropMessageFilter_test.go +++ b/agreement/fuzzer/dropMessageFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/duplicateMessageFilter_test.go b/agreement/fuzzer/duplicateMessageFilter_test.go index 80a4f9b45b..109981040d 100644 --- a/agreement/fuzzer/duplicateMessageFilter_test.go +++ b/agreement/fuzzer/duplicateMessageFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/filter_test.go b/agreement/fuzzer/filter_test.go index b4b0b7972d..6439802387 100644 --- a/agreement/fuzzer/filter_test.go +++ b/agreement/fuzzer/filter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/fuzzer.go b/agreement/fuzzer/fuzzer.go index 847810f455..a5eb67ce54 100644 --- a/agreement/fuzzer/fuzzer.go +++ b/agreement/fuzzer/fuzzer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/fuzzer_test.go b/agreement/fuzzer/fuzzer_test.go index 77a52401e5..eea4a0354f 100644 --- a/agreement/fuzzer/fuzzer_test.go +++ b/agreement/fuzzer/fuzzer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go index a62caee4d9..00ba132294 100644 --- a/agreement/fuzzer/ledger_test.go +++ b/agreement/fuzzer/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageDecoderFilter_test.go b/agreement/fuzzer/messageDecoderFilter_test.go index 03b0af15dd..0482bedea5 100644 --- a/agreement/fuzzer/messageDecoderFilter_test.go +++ b/agreement/fuzzer/messageDecoderFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageDelayFilter_test.go b/agreement/fuzzer/messageDelayFilter_test.go index 02636b50a9..be65409d16 100644 --- a/agreement/fuzzer/messageDelayFilter_test.go +++ b/agreement/fuzzer/messageDelayFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageDuplicationFilter_test.go b/agreement/fuzzer/messageDuplicationFilter_test.go index 4a7d4e88c9..2be51b3d75 100644 --- a/agreement/fuzzer/messageDuplicationFilter_test.go +++ b/agreement/fuzzer/messageDuplicationFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messagePriorityQueue_test.go b/agreement/fuzzer/messagePriorityQueue_test.go index 8638cb48b4..d053321442 100644 --- a/agreement/fuzzer/messagePriorityQueue_test.go +++ b/agreement/fuzzer/messagePriorityQueue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageReflectionFilter_test.go b/agreement/fuzzer/messageReflectionFilter_test.go index 73aeae5b1c..4ec6f86018 100644 --- a/agreement/fuzzer/messageReflectionFilter_test.go +++ b/agreement/fuzzer/messageReflectionFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageRegossipFilter_test.go b/agreement/fuzzer/messageRegossipFilter_test.go index 264de6e647..eef3897491 100644 --- a/agreement/fuzzer/messageRegossipFilter_test.go +++ b/agreement/fuzzer/messageRegossipFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/messageReorderingFilter_test.go b/agreement/fuzzer/messageReorderingFilter_test.go index 23049deaee..e17f12acdb 100644 --- a/agreement/fuzzer/messageReorderingFilter_test.go +++ b/agreement/fuzzer/messageReorderingFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/networkFacade_test.go b/agreement/fuzzer/networkFacade_test.go index 86c7ff7122..131361e3e7 100644 --- a/agreement/fuzzer/networkFacade_test.go +++ b/agreement/fuzzer/networkFacade_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/nodeCrashFilter_test.go b/agreement/fuzzer/nodeCrashFilter_test.go index ef31ca4d88..0f77bc95c2 100644 --- a/agreement/fuzzer/nodeCrashFilter_test.go +++ b/agreement/fuzzer/nodeCrashFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/nullFilter_test.go b/agreement/fuzzer/nullFilter_test.go index e6ee528e17..ee312d744b 100644 --- a/agreement/fuzzer/nullFilter_test.go +++ b/agreement/fuzzer/nullFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/router_test.go b/agreement/fuzzer/router_test.go index 74e39c1ec1..576ac4a3d9 100644 --- a/agreement/fuzzer/router_test.go +++ b/agreement/fuzzer/router_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/schedulerFilter_test.go b/agreement/fuzzer/schedulerFilter_test.go index d6e00e7734..f6d84fee62 100644 --- a/agreement/fuzzer/schedulerFilter_test.go +++ b/agreement/fuzzer/schedulerFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/tests_test.go b/agreement/fuzzer/tests_test.go index fa48979a3f..45583822cb 100644 --- a/agreement/fuzzer/tests_test.go +++ b/agreement/fuzzer/tests_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/topologyFilter_test.go b/agreement/fuzzer/topologyFilter_test.go index db62ff6045..57389cc93f 100644 --- a/agreement/fuzzer/topologyFilter_test.go +++ b/agreement/fuzzer/topologyFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/trafficStatisticsFilter_test.go b/agreement/fuzzer/trafficStatisticsFilter_test.go index 72cad0fc67..61c31ef568 100644 --- a/agreement/fuzzer/trafficStatisticsFilter_test.go +++ b/agreement/fuzzer/trafficStatisticsFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/validator_test.go b/agreement/fuzzer/validator_test.go index 8c7352ccc6..92ac385457 100644 --- a/agreement/fuzzer/validator_test.go +++ b/agreement/fuzzer/validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/fuzzer/voteFilter_test.go b/agreement/fuzzer/voteFilter_test.go index 655708758a..c61affcb07 100644 --- a/agreement/fuzzer/voteFilter_test.go +++ b/agreement/fuzzer/voteFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/gossip/network.go b/agreement/gossip/network.go index 3341882772..644bb10d71 100644 --- a/agreement/gossip/network.go +++ b/agreement/gossip/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/gossip/networkFull_test.go b/agreement/gossip/networkFull_test.go index 784baa8c00..6507a6cb82 100644 --- a/agreement/gossip/networkFull_test.go +++ b/agreement/gossip/networkFull_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/gossip/network_test.go b/agreement/gossip/network_test.go index 8584dc8d26..c168b77cce 100644 --- a/agreement/gossip/network_test.go +++ b/agreement/gossip/network_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/keyManager_test.go b/agreement/keyManager_test.go index 106431c91f..f9337e26d7 100644 --- a/agreement/keyManager_test.go +++ b/agreement/keyManager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/listener.go b/agreement/listener.go index 22ec285854..99e2e3e6bb 100644 --- a/agreement/listener.go +++ b/agreement/listener.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/message.go b/agreement/message.go index 1486dd98e4..49782f4c9c 100644 --- a/agreement/message.go +++ b/agreement/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/message_test.go b/agreement/message_test.go index fb0558fc15..49056c4983 100644 --- a/agreement/message_test.go +++ b/agreement/message_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/params.go b/agreement/params.go index de90d29136..a4f290aa1b 100644 --- a/agreement/params.go +++ b/agreement/params.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/persistence.go b/agreement/persistence.go index b8510e446c..ffb5db8cfa 100644 --- a/agreement/persistence.go +++ b/agreement/persistence.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/persistence_test.go b/agreement/persistence_test.go index a3cc99755d..b039ed7e98 100644 --- a/agreement/persistence_test.go +++ b/agreement/persistence_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/player.go b/agreement/player.go index 4e7ec685e3..c86c465471 100644 --- a/agreement/player.go +++ b/agreement/player.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/playerContract.go b/agreement/playerContract.go index cfc574fef0..45a2b3dee4 100644 --- a/agreement/playerContract.go +++ b/agreement/playerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/player_permutation_test.go b/agreement/player_permutation_test.go index 2c598ff779..e41195d1f1 100644 --- a/agreement/player_permutation_test.go +++ b/agreement/player_permutation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/player_test.go b/agreement/player_test.go index a60c9f44c4..2274156934 100644 --- a/agreement/player_test.go +++ b/agreement/player_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposal.go b/agreement/proposal.go index bf021f2cfe..ac29970b16 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalManager.go b/agreement/proposalManager.go index 71bea3df53..6ec8d2e1b6 100644 --- a/agreement/proposalManager.go +++ b/agreement/proposalManager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalManagerContract.go b/agreement/proposalManagerContract.go index df1593848d..6f6adf888c 100644 --- a/agreement/proposalManagerContract.go +++ b/agreement/proposalManagerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalManager_test.go b/agreement/proposalManager_test.go index 48481bd730..bc5b98409f 100644 --- a/agreement/proposalManager_test.go +++ b/agreement/proposalManager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalStore.go b/agreement/proposalStore.go index fdfecac5f2..d7c4c48f95 100644 --- a/agreement/proposalStore.go +++ b/agreement/proposalStore.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalStoreContract.go b/agreement/proposalStoreContract.go index b1d0192ace..25279de145 100644 --- a/agreement/proposalStoreContract.go +++ b/agreement/proposalStoreContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalStore_test.go b/agreement/proposalStore_test.go index 46f9cd38da..2ce8c23753 100644 --- a/agreement/proposalStore_test.go +++ b/agreement/proposalStore_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTable.go b/agreement/proposalTable.go index a5a5ff2eb1..6659594185 100644 --- a/agreement/proposalTable.go +++ b/agreement/proposalTable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTable_test.go b/agreement/proposalTable_test.go index 2cb5e9f380..329ef09187 100644 --- a/agreement/proposalTable_test.go +++ b/agreement/proposalTable_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTracker.go b/agreement/proposalTracker.go index de89f8e3e9..0a9dce9d8e 100644 --- a/agreement/proposalTracker.go +++ b/agreement/proposalTracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTrackerContract.go b/agreement/proposalTrackerContract.go index a6fa2ffc36..0fbcaadab7 100644 --- a/agreement/proposalTrackerContract.go +++ b/agreement/proposalTrackerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposalTracker_test.go b/agreement/proposalTracker_test.go index 164e9db40b..885933df12 100644 --- a/agreement/proposalTracker_test.go +++ b/agreement/proposalTracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/proposal_test.go b/agreement/proposal_test.go index 3102a08a84..98cb177073 100644 --- a/agreement/proposal_test.go +++ b/agreement/proposal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/pseudonode.go b/agreement/pseudonode.go index 78f6674d7b..e0bfb326bd 100644 --- a/agreement/pseudonode.go +++ b/agreement/pseudonode.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/pseudonode_test.go b/agreement/pseudonode_test.go index a60a1e5efa..ee83061ba0 100644 --- a/agreement/pseudonode_test.go +++ b/agreement/pseudonode_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/router.go b/agreement/router.go index 86b9a18774..fa7e8dd634 100644 --- a/agreement/router.go +++ b/agreement/router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/selector.go b/agreement/selector.go index ba90be3b89..76f9d5a312 100644 --- a/agreement/selector.go +++ b/agreement/selector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/service.go b/agreement/service.go index 852f171455..d8ec84b92f 100644 --- a/agreement/service.go +++ b/agreement/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/service_test.go b/agreement/service_test.go index 0a7ef55f96..5cc05901ed 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/sort.go b/agreement/sort.go index 12a0780746..e9455336cc 100644 --- a/agreement/sort.go +++ b/agreement/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/sort_test.go b/agreement/sort_test.go index be2b968ddc..ef7c6ab7e9 100644 --- a/agreement/sort_test.go +++ b/agreement/sort_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/state_machine_test.go b/agreement/state_machine_test.go index 6946a051ec..ee2926c203 100644 --- a/agreement/state_machine_test.go +++ b/agreement/state_machine_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/trace.go b/agreement/trace.go index d5aeb3d9dd..2409d103f4 100644 --- a/agreement/trace.go +++ b/agreement/trace.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/traceTime.go b/agreement/traceTime.go index d397a66fb4..ed38abe55c 100644 --- a/agreement/traceTime.go +++ b/agreement/traceTime.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/types.go b/agreement/types.go index 000f03e1ea..a2ca872bb5 100644 --- a/agreement/types.go +++ b/agreement/types.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/vote.go b/agreement/vote.go index bed20fe88f..5dad87c85c 100644 --- a/agreement/vote.go +++ b/agreement/vote.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAggregator.go b/agreement/voteAggregator.go index b031ac9faf..c5b711a6ee 100644 --- a/agreement/voteAggregator.go +++ b/agreement/voteAggregator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAggregatorContract.go b/agreement/voteAggregatorContract.go index 5ebdb56929..73eb9c83c0 100644 --- a/agreement/voteAggregatorContract.go +++ b/agreement/voteAggregatorContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAggregator_test.go b/agreement/voteAggregator_test.go index 554426ecb1..2a1eee17a9 100644 --- a/agreement/voteAggregator_test.go +++ b/agreement/voteAggregator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAuxiliary.go b/agreement/voteAuxiliary.go index fa8bfd36b7..c1c9402cb3 100644 --- a/agreement/voteAuxiliary.go +++ b/agreement/voteAuxiliary.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAuxiliaryContract.go b/agreement/voteAuxiliaryContract.go index ada331a6e4..c8c00810bc 100644 --- a/agreement/voteAuxiliaryContract.go +++ b/agreement/voteAuxiliaryContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteAuxiliary_test.go b/agreement/voteAuxiliary_test.go index c20fecc62d..852aa30d8a 100644 --- a/agreement/voteAuxiliary_test.go +++ b/agreement/voteAuxiliary_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteTracker.go b/agreement/voteTracker.go index f1250566a9..6b0f9453c0 100644 --- a/agreement/voteTracker.go +++ b/agreement/voteTracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteTrackerContract.go b/agreement/voteTrackerContract.go index 31470caa41..ef1e6c671a 100644 --- a/agreement/voteTrackerContract.go +++ b/agreement/voteTrackerContract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/voteTracker_test.go b/agreement/voteTracker_test.go index 75359fa342..3ed1454d76 100644 --- a/agreement/voteTracker_test.go +++ b/agreement/voteTracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/agreement/vote_test.go b/agreement/vote_test.go index 22b1049be1..e5da1842e7 100644 --- a/agreement/vote_test.go +++ b/agreement/vote_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index a0a22c5e33..f71a4209ae 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/catchpointService_test.go b/catchup/catchpointService_test.go index 34f1adf0fd..39cef9f2b9 100644 --- a/catchup/catchpointService_test.go +++ b/catchup/catchpointService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/fetcher_test.go b/catchup/fetcher_test.go index 983de01475..dab8da4688 100644 --- a/catchup/fetcher_test.go +++ b/catchup/fetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/ledgerFetcher.go b/catchup/ledgerFetcher.go index 43a039a09e..2f87b5d576 100644 --- a/catchup/ledgerFetcher.go +++ b/catchup/ledgerFetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/ledgerFetcher_test.go b/catchup/ledgerFetcher_test.go index f5039e9912..6bbde32120 100644 --- a/catchup/ledgerFetcher_test.go +++ b/catchup/ledgerFetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index e9f256e1ba..4ceda8d42d 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index 680b65cf37..2a907934a8 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/pref_test.go b/catchup/pref_test.go index be3f67f473..38b2a9d16e 100644 --- a/catchup/pref_test.go +++ b/catchup/pref_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/service.go b/catchup/service.go index 01ff1678ef..1093f851a2 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/service_test.go b/catchup/service_test.go index c712fab5a3..c5b8cfccab 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index 6d9fcce8de..926c85bb48 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/catchup/universalFetcher_test.go b/catchup/universalFetcher_test.go index c8dcbd9840..59c5d69b7a 100644 --- a/catchup/universalFetcher_test.go +++ b/catchup/universalFetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/getCommand.go b/cmd/algocfg/getCommand.go index 016de6cd34..0954177934 100644 --- a/cmd/algocfg/getCommand.go +++ b/cmd/algocfg/getCommand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/getCommand_test.go b/cmd/algocfg/getCommand_test.go index ad1525b7d7..52587f7f90 100644 --- a/cmd/algocfg/getCommand_test.go +++ b/cmd/algocfg/getCommand_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/main.go b/cmd/algocfg/main.go index 9fe71879e3..94e81256c4 100644 --- a/cmd/algocfg/main.go +++ b/cmd/algocfg/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 0a368fc975..1a28587ee7 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go index dd905fd857..afe9fd4f8e 100644 --- a/cmd/algocfg/profileCommand_test.go +++ b/cmd/algocfg/profileCommand_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/report.go b/cmd/algocfg/report.go index a688a04edd..27ce95ffcf 100644 --- a/cmd/algocfg/report.go +++ b/cmd/algocfg/report.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/resetCommand.go b/cmd/algocfg/resetCommand.go index 2ec8c55aad..079b2e164f 100644 --- a/cmd/algocfg/resetCommand.go +++ b/cmd/algocfg/resetCommand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algocfg/setCommand.go b/cmd/algocfg/setCommand.go index 58f7ee796d..704237b41c 100644 --- a/cmd/algocfg/setCommand.go +++ b/cmd/algocfg/setCommand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 5450ff5b64..603f543b89 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algod/main_test.go b/cmd/algod/main_test.go index e2a37a8f87..917f2d038d 100644 --- a/cmd/algod/main_test.go +++ b/cmd/algod/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algofix/deadlock.go b/cmd/algofix/deadlock.go index c6a9757647..1255e38027 100644 --- a/cmd/algofix/deadlock.go +++ b/cmd/algofix/deadlock.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algofix/deadlock_test.go b/cmd/algofix/deadlock_test.go index 7d23edb7fd..66fcce29dd 100644 --- a/cmd/algofix/deadlock_test.go +++ b/cmd/algofix/deadlock_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockWatcher.go b/cmd/algoh/blockWatcher.go index bcfb519a88..d45609182d 100644 --- a/cmd/algoh/blockWatcher.go +++ b/cmd/algoh/blockWatcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockWatcher_test.go b/cmd/algoh/blockWatcher_test.go index 79086d097c..3daa916dd7 100644 --- a/cmd/algoh/blockWatcher_test.go +++ b/cmd/algoh/blockWatcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockstats.go b/cmd/algoh/blockstats.go index fd54bdff4c..a91536dbe2 100644 --- a/cmd/algoh/blockstats.go +++ b/cmd/algoh/blockstats.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/blockstats_test.go b/cmd/algoh/blockstats_test.go index abd3e7eeaa..a2ac99ec81 100644 --- a/cmd/algoh/blockstats_test.go +++ b/cmd/algoh/blockstats_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/client.go b/cmd/algoh/client.go index a0a12194d5..20924f9394 100644 --- a/cmd/algoh/client.go +++ b/cmd/algoh/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/deadman.go b/cmd/algoh/deadman.go index 6b0e0e1bf0..ffd4d556b3 100644 --- a/cmd/algoh/deadman.go +++ b/cmd/algoh/deadman.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/eventsender.go b/cmd/algoh/eventsender.go index 678dd203a2..111b94a2ae 100644 --- a/cmd/algoh/eventsender.go +++ b/cmd/algoh/eventsender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/main.go b/cmd/algoh/main.go index c57c28ba9a..71ebb368cc 100644 --- a/cmd/algoh/main.go +++ b/cmd/algoh/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algoh/mockClient.go b/cmd/algoh/mockClient.go index 45e081e1e1..5370e78d86 100644 --- a/cmd/algoh/mockClient.go +++ b/cmd/algoh/mockClient.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/common.go b/cmd/algokey/common.go index 71b58498c9..8fc5867db9 100644 --- a/cmd/algokey/common.go +++ b/cmd/algokey/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/export.go b/cmd/algokey/export.go index 2966eabd10..39fcf9440d 100644 --- a/cmd/algokey/export.go +++ b/cmd/algokey/export.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/generate.go b/cmd/algokey/generate.go index 4d42735854..6382fa3a0c 100644 --- a/cmd/algokey/generate.go +++ b/cmd/algokey/generate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/import.go b/cmd/algokey/import.go index 34f90ca2e0..53f40d02d0 100644 --- a/cmd/algokey/import.go +++ b/cmd/algokey/import.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/keyreg.go b/cmd/algokey/keyreg.go index 4393664420..43b072f4d0 100644 --- a/cmd/algokey/keyreg.go +++ b/cmd/algokey/keyreg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/main.go b/cmd/algokey/main.go index c04b73a3ca..2c0aa1644b 100644 --- a/cmd/algokey/main.go +++ b/cmd/algokey/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/multisig.go b/cmd/algokey/multisig.go index ef51d5dac1..543735b5d8 100644 --- a/cmd/algokey/multisig.go +++ b/cmd/algokey/multisig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/part.go b/cmd/algokey/part.go index 76148ae1f6..cd49f73af7 100644 --- a/cmd/algokey/part.go +++ b/cmd/algokey/part.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algokey/sign.go b/cmd/algokey/sign.go index 3d8bf6dbcb..04b6c3bee2 100644 --- a/cmd/algokey/sign.go +++ b/cmd/algokey/sign.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algons/commands.go b/cmd/algons/commands.go index 3e010951dc..a1d17029de 100644 --- a/cmd/algons/commands.go +++ b/cmd/algons/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algons/dnsCmd.go b/cmd/algons/dnsCmd.go index 97b3b5f334..da402c3326 100644 --- a/cmd/algons/dnsCmd.go +++ b/cmd/algons/dnsCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algons/dnsaddrCmd.go b/cmd/algons/dnsaddrCmd.go index 1d9189082b..c8fb1ac52b 100644 --- a/cmd/algons/dnsaddrCmd.go +++ b/cmd/algons/dnsaddrCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algorelay/commands.go b/cmd/algorelay/commands.go index 8199c742c2..99c5c80879 100644 --- a/cmd/algorelay/commands.go +++ b/cmd/algorelay/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algorelay/eb/eb.go b/cmd/algorelay/eb/eb.go index c03ef85b1f..348d729508 100644 --- a/cmd/algorelay/eb/eb.go +++ b/cmd/algorelay/eb/eb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/algorelay/relayCmd.go b/cmd/algorelay/relayCmd.go index d658daa811..073147e4f8 100644 --- a/cmd/algorelay/relayCmd.go +++ b/cmd/algorelay/relayCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/buildtools/commands.go b/cmd/buildtools/commands.go index 92a73124cb..0920d05ab5 100644 --- a/cmd/buildtools/commands.go +++ b/cmd/buildtools/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/buildtools/genesis.go b/cmd/buildtools/genesis.go index 19712d4232..853a08489c 100644 --- a/cmd/buildtools/genesis.go +++ b/cmd/buildtools/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchpointdump/commands.go b/cmd/catchpointdump/commands.go index 82deb897f1..f9dae40186 100644 --- a/cmd/catchpointdump/commands.go +++ b/cmd/catchpointdump/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchpointdump/database.go b/cmd/catchpointdump/database.go index 7706bcd3dd..31f18cc867 100644 --- a/cmd/catchpointdump/database.go +++ b/cmd/catchpointdump/database.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchpointdump/file.go b/cmd/catchpointdump/file.go index 2e3580b761..f0f7c7bff5 100644 --- a/cmd/catchpointdump/file.go +++ b/cmd/catchpointdump/file.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchpointdump/net.go b/cmd/catchpointdump/net.go index 77c28870fc..2de40c3c2d 100644 --- a/cmd/catchpointdump/net.go +++ b/cmd/catchpointdump/net.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/download.go b/cmd/catchupsrv/download.go index 160605fe57..80c1cc679b 100644 --- a/cmd/catchupsrv/download.go +++ b/cmd/catchupsrv/download.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/download_test.go b/cmd/catchupsrv/download_test.go index 975f321655..18ff841045 100644 --- a/cmd/catchupsrv/download_test.go +++ b/cmd/catchupsrv/download_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/main.go b/cmd/catchupsrv/main.go index f89c69ad07..91fa4b0702 100644 --- a/cmd/catchupsrv/main.go +++ b/cmd/catchupsrv/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/catchupsrv/tarblocks.go b/cmd/catchupsrv/tarblocks.go index d5957d5a21..c1498fec05 100644 --- a/cmd/catchupsrv/tarblocks.go +++ b/cmd/catchupsrv/tarblocks.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/dbgen/main.go b/cmd/dbgen/main.go index 0bcc3e93c6..3c486ba224 100644 --- a/cmd/dbgen/main.go +++ b/cmd/dbgen/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/main.go b/cmd/diagcfg/main.go index 6953daf780..fc81c6791c 100644 --- a/cmd/diagcfg/main.go +++ b/cmd/diagcfg/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/messages.go b/cmd/diagcfg/messages.go index 45acf2e3df..75174fc697 100644 --- a/cmd/diagcfg/messages.go +++ b/cmd/diagcfg/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/metric.go b/cmd/diagcfg/metric.go index d5c44189a3..4e802ba726 100644 --- a/cmd/diagcfg/metric.go +++ b/cmd/diagcfg/metric.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/diagcfg/telemetry.go b/cmd/diagcfg/telemetry.go index fac9bb46f4..a4aece54f6 100644 --- a/cmd/diagcfg/telemetry.go +++ b/cmd/diagcfg/telemetry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/dispenser/server.go b/cmd/dispenser/server.go index 064d9f58e2..5501a69b74 100644 --- a/cmd/dispenser/server.go +++ b/cmd/dispenser/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/genesis/newgenesis.go b/cmd/genesis/newgenesis.go index 25d5f92d81..bce6ca3dd2 100644 --- a/cmd/genesis/newgenesis.go +++ b/cmd/genesis/newgenesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 60b8ce72d2..9fe230f670 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/accountsList.go b/cmd/goal/accountsList.go index 390d3b2b07..5e4a2e6fa8 100644 --- a/cmd/goal/accountsList.go +++ b/cmd/goal/accountsList.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 2aebe89de8..922540b726 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/application_test.go b/cmd/goal/application_test.go index d037f77bdd..1950337fdd 100644 --- a/cmd/goal/application_test.go +++ b/cmd/goal/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/asset.go b/cmd/goal/asset.go index 64c2d4c56c..d67e4f4acb 100644 --- a/cmd/goal/asset.go +++ b/cmd/goal/asset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/box.go b/cmd/goal/box.go index 4fd6495fa6..c32b631263 100644 --- a/cmd/goal/box.go +++ b/cmd/goal/box.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 40b93a7f94..a69ed5be98 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/clerk_test.go b/cmd/goal/clerk_test.go index ab63e699ea..2efc67f3a7 100644 --- a/cmd/goal/clerk_test.go +++ b/cmd/goal/clerk_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/commands.go b/cmd/goal/commands.go index a12f08b2e6..3ba9891d7d 100644 --- a/cmd/goal/commands.go +++ b/cmd/goal/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/commands_test.go b/cmd/goal/commands_test.go index 8f5d02510c..986ade2712 100644 --- a/cmd/goal/commands_test.go +++ b/cmd/goal/commands_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/common.go b/cmd/goal/common.go index 3c4189dbf1..0a8637f98a 100644 --- a/cmd/goal/common.go +++ b/cmd/goal/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/completion.go b/cmd/goal/completion.go index 418b1c8570..0a49f69b4e 100644 --- a/cmd/goal/completion.go +++ b/cmd/goal/completion.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/formatting.go b/cmd/goal/formatting.go index 9eebf3fbb8..32484412fe 100644 --- a/cmd/goal/formatting.go +++ b/cmd/goal/formatting.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/formatting_test.go b/cmd/goal/formatting_test.go index 29d27483aa..1247cddf14 100644 --- a/cmd/goal/formatting_test.go +++ b/cmd/goal/formatting_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/inspect.go b/cmd/goal/inspect.go index 9f8c078d1c..7f57e26f0a 100644 --- a/cmd/goal/inspect.go +++ b/cmd/goal/inspect.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/inspect_test.go b/cmd/goal/inspect_test.go index 9a153bebb1..7feb1ed03c 100644 --- a/cmd/goal/inspect_test.go +++ b/cmd/goal/inspect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go index ca79daf0d4..e2f0226e90 100644 --- a/cmd/goal/interact.go +++ b/cmd/goal/interact.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/kmd.go b/cmd/goal/kmd.go index 517d975fa9..4076cc8aca 100644 --- a/cmd/goal/kmd.go +++ b/cmd/goal/kmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/ledger.go b/cmd/goal/ledger.go index 84a92352ff..586b28b07c 100644 --- a/cmd/goal/ledger.go +++ b/cmd/goal/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/logging.go b/cmd/goal/logging.go index 68c0a696c4..c1894cef5a 100644 --- a/cmd/goal/logging.go +++ b/cmd/goal/logging.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/messages.go b/cmd/goal/messages.go index bcb7b3fd52..1bc78ce30a 100644 --- a/cmd/goal/messages.go +++ b/cmd/goal/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/multisig.go b/cmd/goal/multisig.go index 88b9a45638..a1e9b60494 100644 --- a/cmd/goal/multisig.go +++ b/cmd/goal/multisig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/network.go b/cmd/goal/network.go index a63bb13f58..a0d8cf4241 100644 --- a/cmd/goal/network.go +++ b/cmd/goal/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/node.go b/cmd/goal/node.go index 1258001ed1..17de96a81a 100644 --- a/cmd/goal/node.go +++ b/cmd/goal/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/node_test.go b/cmd/goal/node_test.go index e34568202d..bdbcf31b54 100644 --- a/cmd/goal/node_test.go +++ b/cmd/goal/node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/tealsign.go b/cmd/goal/tealsign.go index 45fbd3a988..9cad30a9fc 100644 --- a/cmd/goal/tealsign.go +++ b/cmd/goal/tealsign.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/goal/wallet.go b/cmd/goal/wallet.go index 0b55c18f14..e95aea62a6 100644 --- a/cmd/goal/wallet.go +++ b/cmd/goal/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/incorporate/incorporate.go b/cmd/incorporate/incorporate.go index 97d4b44d66..b9f7dd55b1 100644 --- a/cmd/incorporate/incorporate.go +++ b/cmd/incorporate/incorporate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/codes/codes.go b/cmd/kmd/codes/codes.go index ecfe573529..e49c456f0d 100644 --- a/cmd/kmd/codes/codes.go +++ b/cmd/kmd/codes/codes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/main.go b/cmd/kmd/main.go index 92bfe62ac9..711ee75146 100644 --- a/cmd/kmd/main.go +++ b/cmd/kmd/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/mlock_darwin.go b/cmd/kmd/mlock_darwin.go index 5e6e75cccc..cc9d7fde6b 100644 --- a/cmd/kmd/mlock_darwin.go +++ b/cmd/kmd/mlock_darwin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/mlock_linux.go b/cmd/kmd/mlock_linux.go index c650ec15c7..04e6b4232e 100644 --- a/cmd/kmd/mlock_linux.go +++ b/cmd/kmd/mlock_linux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/kmd/mlock_windows.go b/cmd/kmd/mlock_windows.go index 20db381e2c..29f9b14778 100644 --- a/cmd/kmd/mlock_windows.go +++ b/cmd/kmd/mlock_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/loadgenerator/config.go b/cmd/loadgenerator/config.go index 5ffb86fd74..f31296f354 100644 --- a/cmd/loadgenerator/config.go +++ b/cmd/loadgenerator/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/loadgenerator/main.go b/cmd/loadgenerator/main.go index 9b4bb61052..6b82887695 100644 --- a/cmd/loadgenerator/main.go +++ b/cmd/loadgenerator/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/msgpacktool/main.go b/cmd/msgpacktool/main.go index b6e359a0df..80ae61995c 100644 --- a/cmd/msgpacktool/main.go +++ b/cmd/msgpacktool/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netdummy/main.go b/cmd/netdummy/main.go index cdc85e4167..ef8737368f 100644 --- a/cmd/netdummy/main.go +++ b/cmd/netdummy/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/commands.go b/cmd/netgoal/commands.go index 6fba6f8fa5..ebdf404132 100644 --- a/cmd/netgoal/commands.go +++ b/cmd/netgoal/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/generate.go b/cmd/netgoal/generate.go index 4757cdc6a0..882cd0f9d0 100644 --- a/cmd/netgoal/generate.go +++ b/cmd/netgoal/generate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/messages.go b/cmd/netgoal/messages.go index a575c47351..f75c639b92 100644 --- a/cmd/netgoal/messages.go +++ b/cmd/netgoal/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/network.go b/cmd/netgoal/network.go index 6fa6b317d6..69fecbac26 100644 --- a/cmd/netgoal/network.go +++ b/cmd/netgoal/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/netgoal/recipe.go b/cmd/netgoal/recipe.go index c1356b73f8..0606a253a5 100644 --- a/cmd/netgoal/recipe.go +++ b/cmd/netgoal/recipe.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/nodecfg/apply.go b/cmd/nodecfg/apply.go index 689476537c..49d812ead1 100644 --- a/cmd/nodecfg/apply.go +++ b/cmd/nodecfg/apply.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/nodecfg/commands.go b/cmd/nodecfg/commands.go index ef6b908185..8e492d73f0 100644 --- a/cmd/nodecfg/commands.go +++ b/cmd/nodecfg/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/nodecfg/download.go b/cmd/nodecfg/download.go index fd59e0dff2..1be8fc4d76 100644 --- a/cmd/nodecfg/download.go +++ b/cmd/nodecfg/download.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/nodecfg/get.go b/cmd/nodecfg/get.go index 6ca11c7363..d21f84c06b 100644 --- a/cmd/nodecfg/get.go +++ b/cmd/nodecfg/get.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 8027c9a875..a6e77504b5 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/opdoc/tmLanguage.go b/cmd/opdoc/tmLanguage.go index 63c558ea3d..fce6388591 100644 --- a/cmd/opdoc/tmLanguage.go +++ b/cmd/opdoc/tmLanguage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/partitiontest_linter/linter.go b/cmd/partitiontest_linter/linter.go index 50c1298a09..7a000e5aba 100644 --- a/cmd/partitiontest_linter/linter.go +++ b/cmd/partitiontest_linter/linter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/partitiontest_linter/linter_test.go b/cmd/partitiontest_linter/linter_test.go index 26a3e85be9..a586c9d0b8 100644 --- a/cmd/partitiontest_linter/linter_test.go +++ b/cmd/partitiontest_linter/linter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/partitiontest_linter/plugin/plugin.go b/cmd/partitiontest_linter/plugin/plugin.go index ee229371f2..9a895ce90e 100644 --- a/cmd/partitiontest_linter/plugin/plugin.go +++ b/cmd/partitiontest_linter/plugin/plugin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/partitiontest_linter/testdata/linter_testdata_test.go b/cmd/partitiontest_linter/testdata/linter_testdata_test.go index d84cb837c2..308f7f1803 100644 --- a/cmd/partitiontest_linter/testdata/linter_testdata_test.go +++ b/cmd/partitiontest_linter/testdata/linter_testdata_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/pingpong/commands.go b/cmd/pingpong/commands.go index 7e78eedb03..fb44d8b076 100644 --- a/cmd/pingpong/commands.go +++ b/cmd/pingpong/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/pingpong/runCmd.go b/cmd/pingpong/runCmd.go index d141be2608..a29a233c8d 100644 --- a/cmd/pingpong/runCmd.go +++ b/cmd/pingpong/runCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/pingpong/teal_programs.go b/cmd/pingpong/teal_programs.go index 150877188b..cd54fc1ec8 100644 --- a/cmd/pingpong/teal_programs.go +++ b/cmd/pingpong/teal_programs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/bundle_home_html.sh b/cmd/tealdbg/bundle_home_html.sh index 8c83a4aaab..eff5acd41b 100755 --- a/cmd/tealdbg/bundle_home_html.sh +++ b/cmd/tealdbg/bundle_home_html.sh @@ -3,7 +3,7 @@ THISDIR=$(dirname $0) cat < $THISDIR/homepage.go -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdt/proto.go b/cmd/tealdbg/cdt/proto.go index dd28fcd829..f15a45d257 100644 --- a/cmd/tealdbg/cdt/proto.go +++ b/cmd/tealdbg/cdt/proto.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtSession.go b/cmd/tealdbg/cdtSession.go index 5d0bdd08be..ad4e879e7c 100644 --- a/cmd/tealdbg/cdtSession.go +++ b/cmd/tealdbg/cdtSession.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtSession_test.go b/cmd/tealdbg/cdtSession_test.go index 186e0d7df4..6c755511cf 100644 --- a/cmd/tealdbg/cdtSession_test.go +++ b/cmd/tealdbg/cdtSession_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtState.go b/cmd/tealdbg/cdtState.go index 2ee335a21b..65d3e4fb16 100644 --- a/cmd/tealdbg/cdtState.go +++ b/cmd/tealdbg/cdtState.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtStateObjects.go b/cmd/tealdbg/cdtStateObjects.go index 377492d23c..d329156729 100644 --- a/cmd/tealdbg/cdtStateObjects.go +++ b/cmd/tealdbg/cdtStateObjects.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtdbg.go b/cmd/tealdbg/cdtdbg.go index 45de17d3eb..470aec2a59 100644 --- a/cmd/tealdbg/cdtdbg.go +++ b/cmd/tealdbg/cdtdbg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/cdtdbg_test.go b/cmd/tealdbg/cdtdbg_test.go index 0ef838f954..a8c11cc3f6 100644 --- a/cmd/tealdbg/cdtdbg_test.go +++ b/cmd/tealdbg/cdtdbg_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/debugger.go b/cmd/tealdbg/debugger.go index 86674a45d9..0f70e0814c 100644 --- a/cmd/tealdbg/debugger.go +++ b/cmd/tealdbg/debugger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/debugger_test.go b/cmd/tealdbg/debugger_test.go index 6b6d39e698..9a03362ab7 100644 --- a/cmd/tealdbg/debugger_test.go +++ b/cmd/tealdbg/debugger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/dryrunRequest.go b/cmd/tealdbg/dryrunRequest.go index 5f13aa6cfe..958b43f87c 100644 --- a/cmd/tealdbg/dryrunRequest.go +++ b/cmd/tealdbg/dryrunRequest.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/homepage.go b/cmd/tealdbg/homepage.go index 7e77ed291d..f0c6a9bb45 100644 --- a/cmd/tealdbg/homepage.go +++ b/cmd/tealdbg/homepage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/local.go b/cmd/tealdbg/local.go index 61050348e4..6f61fb2da3 100644 --- a/cmd/tealdbg/local.go +++ b/cmd/tealdbg/local.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go index fc7655173f..d97c8df70f 100644 --- a/cmd/tealdbg/localLedger.go +++ b/cmd/tealdbg/localLedger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/localLedger_test.go b/cmd/tealdbg/localLedger_test.go index 38258c54d5..79423c834b 100644 --- a/cmd/tealdbg/localLedger_test.go +++ b/cmd/tealdbg/localLedger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/local_test.go b/cmd/tealdbg/local_test.go index d0f6a3c2c3..c290b67142 100644 --- a/cmd/tealdbg/local_test.go +++ b/cmd/tealdbg/local_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/main.go b/cmd/tealdbg/main.go index 461ed0d03c..313f4da054 100644 --- a/cmd/tealdbg/main.go +++ b/cmd/tealdbg/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/remote.go b/cmd/tealdbg/remote.go index 0656e418df..b845c2d3a7 100644 --- a/cmd/tealdbg/remote.go +++ b/cmd/tealdbg/remote.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/remote_test.go b/cmd/tealdbg/remote_test.go index bf76e9c0da..99b4b67c05 100644 --- a/cmd/tealdbg/remote_test.go +++ b/cmd/tealdbg/remote_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/server.go b/cmd/tealdbg/server.go index 695cb030df..ec457443bf 100644 --- a/cmd/tealdbg/server.go +++ b/cmd/tealdbg/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/server_test.go b/cmd/tealdbg/server_test.go index f9cc023a18..492ebfab7a 100644 --- a/cmd/tealdbg/server_test.go +++ b/cmd/tealdbg/server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/util.go b/cmd/tealdbg/util.go index 971a08223a..fe4fcd05d3 100644 --- a/cmd/tealdbg/util.go +++ b/cmd/tealdbg/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/webdbg.go b/cmd/tealdbg/webdbg.go index d237d70afc..2012127c64 100644 --- a/cmd/tealdbg/webdbg.go +++ b/cmd/tealdbg/webdbg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/tealdbg/webdbg_test.go b/cmd/tealdbg/webdbg_test.go index 40ddd41f84..36962ba8d4 100644 --- a/cmd/tealdbg/webdbg_test.go +++ b/cmd/tealdbg/webdbg_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/commands.go b/cmd/updater/commands.go index cc152ff5d6..335a149caa 100644 --- a/cmd/updater/commands.go +++ b/cmd/updater/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/sendCmd.go b/cmd/updater/sendCmd.go index d194d8ca9b..116340d93d 100644 --- a/cmd/updater/sendCmd.go +++ b/cmd/updater/sendCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/toolsCmd.go b/cmd/updater/toolsCmd.go index 4ebaa8b4ae..1a44f6805c 100644 --- a/cmd/updater/toolsCmd.go +++ b/cmd/updater/toolsCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/util.go b/cmd/updater/util.go index 14fa62cf54..d33c2ce1e9 100644 --- a/cmd/updater/util.go +++ b/cmd/updater/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/versionCmd.go b/cmd/updater/versionCmd.go index 35713f9b39..374fd982ac 100644 --- a/cmd/updater/versionCmd.go +++ b/cmd/updater/versionCmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/updater/version_test.go b/cmd/updater/version_test.go index e46bd2f3e4..6f5418e30f 100644 --- a/cmd/updater/version_test.go +++ b/cmd/updater/version_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/util/cmd.go b/cmd/util/cmd.go index 3b8b1c8841..780aa685b2 100644 --- a/cmd/util/cmd.go +++ b/cmd/util/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/util/datadir/datadir.go b/cmd/util/datadir/datadir.go index 20ba92b8e3..41928b97e1 100644 --- a/cmd/util/datadir/datadir.go +++ b/cmd/util/datadir/datadir.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/util/datadir/messages.go b/cmd/util/datadir/messages.go index 0ef5e6a49a..e70965e89e 100644 --- a/cmd/util/datadir/messages.go +++ b/cmd/util/datadir/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/cmd/util/datadir/report.go b/cmd/util/datadir/report.go index c75aa4924e..d5e2e98149 100644 --- a/cmd/util/datadir/report.go +++ b/cmd/util/datadir/report.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/components/mocks/mockCatchpointCatchupAccessor.go b/components/mocks/mockCatchpointCatchupAccessor.go index d095b703ac..edf7946743 100644 --- a/components/mocks/mockCatchpointCatchupAccessor.go +++ b/components/mocks/mockCatchpointCatchupAccessor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/components/mocks/mockNetwork.go b/components/mocks/mockNetwork.go index 8c8eb113f1..25486cb050 100644 --- a/components/mocks/mockNetwork.go +++ b/components/mocks/mockNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/components/mocks/mockParticipationRegistry.go b/components/mocks/mockParticipationRegistry.go index 6af12ef1dd..525c8b1cd3 100644 --- a/components/mocks/mockParticipationRegistry.go +++ b/components/mocks/mockParticipationRegistry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/buildvars.go b/config/buildvars.go index af0bfe523e..011d27b64d 100644 --- a/config/buildvars.go +++ b/config/buildvars.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/config.go b/config/config.go index ad363f3644..c5dc5d9541 100644 --- a/config/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/config_test.go b/config/config_test.go index ef58bffb9d..914e1e8e9a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/consensus.go b/config/consensus.go index a1baa92044..fc08e2990e 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/consensus_test.go b/config/consensus_test.go index 98b5e874c7..d7fbed4085 100644 --- a/config/consensus_test.go +++ b/config/consensus_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/defaultsGenerator/defaultsGenerator.go b/config/defaultsGenerator/defaultsGenerator.go index d497ea9f27..9afe0d01bd 100644 --- a/config/defaultsGenerator/defaultsGenerator.go +++ b/config/defaultsGenerator/defaultsGenerator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/dnsbootstrap.go b/config/dnsbootstrap.go index 2f45d2dd86..3b027df9c3 100644 --- a/config/dnsbootstrap.go +++ b/config/dnsbootstrap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/dnsbootstrap_test.go b/config/dnsbootstrap_test.go index 431d321d0f..ccf0f9023f 100644 --- a/config/dnsbootstrap_test.go +++ b/config/dnsbootstrap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/keyfile.go b/config/keyfile.go index 0f158e1fda..4e29b74333 100644 --- a/config/keyfile.go +++ b/config/keyfile.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/localTemplate.go b/config/localTemplate.go index 25c6edab05..c6e1ba0a2c 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/local_defaults.go b/config/local_defaults.go index 3df773a760..791e6e2225 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/migrate.go b/config/migrate.go index ae9e2a9a13..8a72407743 100644 --- a/config/migrate.go +++ b/config/migrate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/version.go b/config/version.go index f7e656d40d..ea7d417b2f 100644 --- a/config/version.go +++ b/config/version.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/config/version_test.go b/config/version_test.go index 1a37cf185b..8b1d8c17b8 100644 --- a/config/version_test.go +++ b/config/version_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index af7a677ac3..0281e86e87 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/batchverifier_test.go b/crypto/batchverifier_test.go index c572503ff3..2b8fcec0e9 100644 --- a/crypto/batchverifier_test.go +++ b/crypto/batchverifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index f0e8b1d0ef..ee3a23d58f 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/cryptoerror.go b/crypto/cryptoerror.go index 3282f8d4a9..b635f1ea88 100644 --- a/crypto/cryptoerror.go +++ b/crypto/cryptoerror.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/curve25519.go b/crypto/curve25519.go index a8637399d7..0961ed6263 100644 --- a/crypto/curve25519.go +++ b/crypto/curve25519.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/curve25519_test.go b/crypto/curve25519_test.go index acd152cba0..fac0bd6ab9 100644 --- a/crypto/curve25519_test.go +++ b/crypto/curve25519_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/digest.go b/crypto/digest.go index d6f3c27774..967fa68abf 100644 --- a/crypto/digest.go +++ b/crypto/digest.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/encoding_test.go b/crypto/encoding_test.go index 527a56d2cc..0093b6c587 100644 --- a/crypto/encoding_test.go +++ b/crypto/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/falconWrapper.go b/crypto/falconWrapper.go index 0a2c000b9c..4930210425 100644 --- a/crypto/falconWrapper.go +++ b/crypto/falconWrapper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/falconWrapper_test.go b/crypto/falconWrapper_test.go index 45adb124ab..cb251c7d89 100644 --- a/crypto/falconWrapper_test.go +++ b/crypto/falconWrapper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/hashes.go b/crypto/hashes.go index 04db757f39..3e232ed27d 100644 --- a/crypto/hashes.go +++ b/crypto/hashes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/hashes_test.go b/crypto/hashes_test.go index 9f8b57fe3a..2988f9ca1e 100644 --- a/crypto/hashes_test.go +++ b/crypto/hashes_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/memcpy_chk_windows.c b/crypto/memcpy_chk_windows.c index d79118cdf3..3cff6c7da2 100644 --- a/crypto/memcpy_chk_windows.c +++ b/crypto/memcpy_chk_windows.c @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/array.go b/crypto/merklearray/array.go index 4e04b53383..2e1eccb398 100644 --- a/crypto/merklearray/array.go +++ b/crypto/merklearray/array.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/layer.go b/crypto/merklearray/layer.go index 88eed6ffe5..bed28ccf51 100644 --- a/crypto/merklearray/layer.go +++ b/crypto/merklearray/layer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/merkle.go b/crypto/merklearray/merkle.go index 203d92110e..e540586b53 100644 --- a/crypto/merklearray/merkle.go +++ b/crypto/merklearray/merkle.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/merkle_test.go b/crypto/merklearray/merkle_test.go index 0d392dcef6..f62d6c282c 100644 --- a/crypto/merklearray/merkle_test.go +++ b/crypto/merklearray/merkle_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/partial.go b/crypto/merklearray/partial.go index b1aa07c526..baebf4538d 100644 --- a/crypto/merklearray/partial.go +++ b/crypto/merklearray/partial.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/proof.go b/crypto/merklearray/proof.go index 3455baa08f..8caa057bd3 100644 --- a/crypto/merklearray/proof.go +++ b/crypto/merklearray/proof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/proof_test.go b/crypto/merklearray/proof_test.go index a160371550..7ef7ae044b 100644 --- a/crypto/merklearray/proof_test.go +++ b/crypto/merklearray/proof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/vectorCommitmentArray.go b/crypto/merklearray/vectorCommitmentArray.go index c11e295aea..6745aca603 100644 --- a/crypto/merklearray/vectorCommitmentArray.go +++ b/crypto/merklearray/vectorCommitmentArray.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/vectorCommitmentArray_test.go b/crypto/merklearray/vectorCommitmentArray_test.go index e3887880dc..24b1d32e9d 100644 --- a/crypto/merklearray/vectorCommitmentArray_test.go +++ b/crypto/merklearray/vectorCommitmentArray_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklearray/worker.go b/crypto/merklearray/worker.go index b6d273e37b..2c33440679 100644 --- a/crypto/merklearray/worker.go +++ b/crypto/merklearray/worker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/committablePublicKeys.go b/crypto/merklesignature/committablePublicKeys.go index fd4a9f26a1..a636b3f02a 100644 --- a/crypto/merklesignature/committablePublicKeys.go +++ b/crypto/merklesignature/committablePublicKeys.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/committablePublicKeys_test.go b/crypto/merklesignature/committablePublicKeys_test.go index 08108b302f..68463bf8e6 100644 --- a/crypto/merklesignature/committablePublicKeys_test.go +++ b/crypto/merklesignature/committablePublicKeys_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/const.go b/crypto/merklesignature/const.go index 52d98390f7..40817d4215 100644 --- a/crypto/merklesignature/const.go +++ b/crypto/merklesignature/const.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/kats_test.go b/crypto/merklesignature/kats_test.go index 610cdbab91..64b3e8c539 100644 --- a/crypto/merklesignature/kats_test.go +++ b/crypto/merklesignature/kats_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/keysBuilder.go b/crypto/merklesignature/keysBuilder.go index 50498b519a..68ee76ae30 100644 --- a/crypto/merklesignature/keysBuilder.go +++ b/crypto/merklesignature/keysBuilder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/keysBuilder_test.go b/crypto/merklesignature/keysBuilder_test.go index 24f426e703..01dd082fd4 100644 --- a/crypto/merklesignature/keysBuilder_test.go +++ b/crypto/merklesignature/keysBuilder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/merkleSignatureScheme.go b/crypto/merklesignature/merkleSignatureScheme.go index f11ecb9202..61724af4ec 100644 --- a/crypto/merklesignature/merkleSignatureScheme.go +++ b/crypto/merklesignature/merkleSignatureScheme.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/merkleSignatureScheme_test.go b/crypto/merklesignature/merkleSignatureScheme_test.go index b2c82d42d2..5abd9b77cb 100644 --- a/crypto/merklesignature/merkleSignatureScheme_test.go +++ b/crypto/merklesignature/merkleSignatureScheme_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/persistentMerkleSignatureScheme.go b/crypto/merklesignature/persistentMerkleSignatureScheme.go index a5e114a46a..66f1dd055b 100644 --- a/crypto/merklesignature/persistentMerkleSignatureScheme.go +++ b/crypto/merklesignature/persistentMerkleSignatureScheme.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/persistentMerkleSignatureScheme_test.go b/crypto/merklesignature/persistentMerkleSignatureScheme_test.go index 5885f1cf6d..adc22833f1 100644 --- a/crypto/merklesignature/persistentMerkleSignatureScheme_test.go +++ b/crypto/merklesignature/persistentMerkleSignatureScheme_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/posdivs.go b/crypto/merklesignature/posdivs.go index ac152234f4..09b798c780 100644 --- a/crypto/merklesignature/posdivs.go +++ b/crypto/merklesignature/posdivs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merklesignature/posdivs_test.go b/crypto/merklesignature/posdivs_test.go index 394c318705..68f4b77aba 100644 --- a/crypto/merklesignature/posdivs_test.go +++ b/crypto/merklesignature/posdivs_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/bitset.go b/crypto/merkletrie/bitset.go index 3007372abd..f764137a9a 100644 --- a/crypto/merkletrie/bitset.go +++ b/crypto/merkletrie/bitset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/bitset_test.go b/crypto/merkletrie/bitset_test.go index 2fa5a35f92..315b27c952 100644 --- a/crypto/merkletrie/bitset_test.go +++ b/crypto/merkletrie/bitset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/cache.go b/crypto/merkletrie/cache.go index 453e51645e..d4d60b2c39 100644 --- a/crypto/merkletrie/cache.go +++ b/crypto/merkletrie/cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/cache_test.go b/crypto/merkletrie/cache_test.go index 5967f01145..ac08ea442f 100644 --- a/crypto/merkletrie/cache_test.go +++ b/crypto/merkletrie/cache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/committer.go b/crypto/merkletrie/committer.go index 305ce1f663..66dd2c65c8 100644 --- a/crypto/merkletrie/committer.go +++ b/crypto/merkletrie/committer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/committer_test.go b/crypto/merkletrie/committer_test.go index 317261f727..3dc6c39d2a 100644 --- a/crypto/merkletrie/committer_test.go +++ b/crypto/merkletrie/committer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/node.go b/crypto/merkletrie/node.go index 972ef0e1e9..f63d2b12ab 100644 --- a/crypto/merkletrie/node.go +++ b/crypto/merkletrie/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/node_test.go b/crypto/merkletrie/node_test.go index 893fbc86b2..c9fc2e46f4 100644 --- a/crypto/merkletrie/node_test.go +++ b/crypto/merkletrie/node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/trie.go b/crypto/merkletrie/trie.go index 39051699f3..6bb2a51851 100644 --- a/crypto/merkletrie/trie.go +++ b/crypto/merkletrie/trie.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/merkletrie/trie_test.go b/crypto/merkletrie/trie_test.go index f71545962b..7b15d5534e 100644 --- a/crypto/merkletrie/trie_test.go +++ b/crypto/merkletrie/trie_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/multisig.go b/crypto/multisig.go index 92e711d9e4..d5529c149f 100644 --- a/crypto/multisig.go +++ b/crypto/multisig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/multisig_test.go b/crypto/multisig_test.go index 2f630f7fcf..614889c5a7 100644 --- a/crypto/multisig_test.go +++ b/crypto/multisig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index a2db211a79..d9c94da866 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/onetimesig_test.go b/crypto/onetimesig_test.go index 143bb5c162..ca7b641ead 100644 --- a/crypto/onetimesig_test.go +++ b/crypto/onetimesig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/errors.go b/crypto/passphrase/errors.go index 0a50d59c49..84918ce395 100644 --- a/crypto/passphrase/errors.go +++ b/crypto/passphrase/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/passphrase.go b/crypto/passphrase/passphrase.go index 0f0d09d34c..6c1364229b 100644 --- a/crypto/passphrase/passphrase.go +++ b/crypto/passphrase/passphrase.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/passphrase_test.go b/crypto/passphrase/passphrase_test.go index 543ed5fd77..8824116505 100644 --- a/crypto/passphrase/passphrase_test.go +++ b/crypto/passphrase/passphrase_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/passphrase/wordlist.go b/crypto/passphrase/wordlist.go index 670a72b3d7..d2b786f411 100644 --- a/crypto/passphrase/wordlist.go +++ b/crypto/passphrase/wordlist.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/rand.go b/crypto/rand.go index 99f22e1457..e74b62f9dc 100644 --- a/crypto/rand.go +++ b/crypto/rand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/rand_test.go b/crypto/rand_test.go index 129f831714..49d431c8ee 100644 --- a/crypto/rand_test.go +++ b/crypto/rand_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/coinGenerator.go b/crypto/stateproof/coinGenerator.go index eb73e10f86..eb256336dc 100644 --- a/crypto/stateproof/coinGenerator.go +++ b/crypto/stateproof/coinGenerator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/coinGenerator_test.go b/crypto/stateproof/coinGenerator_test.go index d15091f6d0..cf4b326eae 100644 --- a/crypto/stateproof/coinGenerator_test.go +++ b/crypto/stateproof/coinGenerator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/committableSignatureSlot.go b/crypto/stateproof/committableSignatureSlot.go index 2c296e75f1..c53df6960d 100644 --- a/crypto/stateproof/committableSignatureSlot.go +++ b/crypto/stateproof/committableSignatureSlot.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/committableSignatureSlot_test.go b/crypto/stateproof/committableSignatureSlot_test.go index b2519cea6c..5723df5aab 100644 --- a/crypto/stateproof/committableSignatureSlot_test.go +++ b/crypto/stateproof/committableSignatureSlot_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/const.go b/crypto/stateproof/const.go index eefe0d1a93..35c6134e98 100644 --- a/crypto/stateproof/const.go +++ b/crypto/stateproof/const.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/prover.go b/crypto/stateproof/prover.go index f3a8a43bca..45d9e27aa2 100644 --- a/crypto/stateproof/prover.go +++ b/crypto/stateproof/prover.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/prover_test.go b/crypto/stateproof/prover_test.go index b9680de3a0..42a0daa5ed 100644 --- a/crypto/stateproof/prover_test.go +++ b/crypto/stateproof/prover_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/structs.go b/crypto/stateproof/structs.go index 39e5e0f2f0..f82edbd6c0 100644 --- a/crypto/stateproof/structs.go +++ b/crypto/stateproof/structs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/verifier.go b/crypto/stateproof/verifier.go index 67d83068f1..0f36f95a50 100644 --- a/crypto/stateproof/verifier.go +++ b/crypto/stateproof/verifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/verifier_test.go b/crypto/stateproof/verifier_test.go index 23a5fac3ad..352a53202a 100644 --- a/crypto/stateproof/verifier_test.go +++ b/crypto/stateproof/verifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/weights.go b/crypto/stateproof/weights.go index 43a22affdc..874222f493 100644 --- a/crypto/stateproof/weights.go +++ b/crypto/stateproof/weights.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/stateproof/weights_test.go b/crypto/stateproof/weights_test.go index bf1fd2f644..718382563d 100644 --- a/crypto/stateproof/weights_test.go +++ b/crypto/stateproof/weights_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/statetrie/nibbles/nibbles.go b/crypto/statetrie/nibbles/nibbles.go index 8a8409b6b0..d2f95678fd 100644 --- a/crypto/statetrie/nibbles/nibbles.go +++ b/crypto/statetrie/nibbles/nibbles.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/statetrie/nibbles/nibbles_test.go b/crypto/statetrie/nibbles/nibbles_test.go index c088f1dd8a..7b355c0386 100644 --- a/crypto/statetrie/nibbles/nibbles_test.go +++ b/crypto/statetrie/nibbles/nibbles_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/util.go b/crypto/util.go index 078d52c0cd..8d167dfc8b 100644 --- a/crypto/util.go +++ b/crypto/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/util_test.go b/crypto/util_test.go index 2e0828bcce..01941511b3 100644 --- a/crypto/util_test.go +++ b/crypto/util_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/vrf.go b/crypto/vrf.go index 948a84ddc5..3c74225893 100644 --- a/crypto/vrf.go +++ b/crypto/vrf.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/crypto/vrf_test.go b/crypto/vrf_test.go index 22b3e75277..7acc791732 100644 --- a/crypto/vrf_test.go +++ b/crypto/vrf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/client/encoding.go b/daemon/algod/api/client/encoding.go index 365fb7d503..b78ca306fd 100644 --- a/daemon/algod/api/client/encoding.go +++ b/daemon/algod/api/client/encoding.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 6be81c35f5..cf25773fd9 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/handlers.go b/daemon/algod/api/server/common/handlers.go index 938d3ee102..308017e98b 100644 --- a/daemon/algod/api/server/common/handlers.go +++ b/daemon/algod/api/server/common/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/metrics.go b/daemon/algod/api/server/common/metrics.go index 18e6b80274..a3ef24bc85 100644 --- a/daemon/algod/api/server/common/metrics.go +++ b/daemon/algod/api/server/common/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/responses.go b/daemon/algod/api/server/common/responses.go index b775d2040c..0b3a4cd3fe 100644 --- a/daemon/algod/api/server/common/responses.go +++ b/daemon/algod/api/server/common/responses.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/routes.go b/daemon/algod/api/server/common/routes.go index 1a78fe00ac..237179d71d 100644 --- a/daemon/algod/api/server/common/routes.go +++ b/daemon/algod/api/server/common/routes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/test/handlers_test.go b/daemon/algod/api/server/common/test/handlers_test.go index c602b5bde0..a928b4e83f 100644 --- a/daemon/algod/api/server/common/test/handlers_test.go +++ b/daemon/algod/api/server/common/test/handlers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/common/test/helpers.go b/daemon/algod/api/server/common/test/helpers.go index 27d534ad53..638bb81361 100644 --- a/daemon/algod/api/server/common/test/helpers.go +++ b/daemon/algod/api/server/common/test/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/common.go b/daemon/algod/api/server/lib/common.go index e44e624c1b..c3461b25fa 100644 --- a/daemon/algod/api/server/lib/common.go +++ b/daemon/algod/api/server/lib/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/auth.go b/daemon/algod/api/server/lib/middlewares/auth.go index d7cc336cf5..06a7573413 100644 --- a/daemon/algod/api/server/lib/middlewares/auth.go +++ b/daemon/algod/api/server/lib/middlewares/auth.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/auth_test.go b/daemon/algod/api/server/lib/middlewares/auth_test.go index 4bdfa7b85c..75b75bca80 100644 --- a/daemon/algod/api/server/lib/middlewares/auth_test.go +++ b/daemon/algod/api/server/lib/middlewares/auth_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/connectionLimiter.go b/daemon/algod/api/server/lib/middlewares/connectionLimiter.go index 21e6c0bd10..0d5d329598 100644 --- a/daemon/algod/api/server/lib/middlewares/connectionLimiter.go +++ b/daemon/algod/api/server/lib/middlewares/connectionLimiter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go b/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go index b3e5ea06a0..88ebc39b90 100644 --- a/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go +++ b/daemon/algod/api/server/lib/middlewares/connectionLimiter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/cors.go b/daemon/algod/api/server/lib/middlewares/cors.go index 049184aad3..89e88dabb6 100644 --- a/daemon/algod/api/server/lib/middlewares/cors.go +++ b/daemon/algod/api/server/lib/middlewares/cors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/lib/middlewares/logger.go b/daemon/algod/api/server/lib/middlewares/logger.go index 5ede3aa49a..7c3084b2bc 100644 --- a/daemon/algod/api/server/lib/middlewares/logger.go +++ b/daemon/algod/api/server/lib/middlewares/logger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/router.go b/daemon/algod/api/server/router.go index e380cb026d..cd0899c176 100644 --- a/daemon/algod/api/server/router.go +++ b/daemon/algod/api/server/router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/router_test.go b/daemon/algod/api/server/router_test.go index 37fc3be74a..0819309aa3 100644 --- a/daemon/algod/api/server/router_test.go +++ b/daemon/algod/api/server/router_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v1/handlers/errors.go b/daemon/algod/api/server/v1/handlers/errors.go index 09720c78e7..d61f6bd797 100644 --- a/daemon/algod/api/server/v1/handlers/errors.go +++ b/daemon/algod/api/server/v1/handlers/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v1/handlers/handlers.go b/daemon/algod/api/server/v1/handlers/handlers.go index f5b914687b..f7e1ac4cc0 100644 --- a/daemon/algod/api/server/v1/handlers/handlers.go +++ b/daemon/algod/api/server/v1/handlers/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v1/routes/routes.go b/daemon/algod/api/server/v1/routes/routes.go index dc163454e9..88ed20ea1d 100644 --- a/daemon/algod/api/server/v1/routes/routes.go +++ b/daemon/algod/api/server/v1/routes/routes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/account.go b/daemon/algod/api/server/v2/account.go index 31483d63b9..addb81b432 100644 --- a/daemon/algod/api/server/v2/account.go +++ b/daemon/algod/api/server/v2/account.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/account_test.go b/daemon/algod/api/server/v2/account_test.go index ac1abd3b9d..cd3c67499a 100644 --- a/daemon/algod/api/server/v2/account_test.go +++ b/daemon/algod/api/server/v2/account_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index acf5fc94b7..7ce7c6abbf 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index 99e0601185..ad0592f5fd 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go index d85ac7c0bc..ff1bcbaa07 100644 --- a/daemon/algod/api/server/v2/errors.go +++ b/daemon/algod/api/server/v2/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 0282594f4f..69201ca848 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/handlers_test.go b/daemon/algod/api/server/v2/handlers_test.go index a0a50dddbe..148c5de629 100644 --- a/daemon/algod/api/server/v2/handlers_test.go +++ b/daemon/algod/api/server/v2/handlers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index adf187053a..8260e54ffb 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index aa5a7492b6..53ef65256c 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index e2b52fc7c4..400906d299 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index b82212a19d..a16b11031b 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/spec/common/model.go b/daemon/algod/api/spec/common/model.go index 4b4d4eae77..016af9a889 100644 --- a/daemon/algod/api/spec/common/model.go +++ b/daemon/algod/api/spec/common/model.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/spec/v2/model.go b/daemon/algod/api/spec/v2/model.go index 845bc7c227..b8b01a2405 100644 --- a/daemon/algod/api/spec/v2/model.go +++ b/daemon/algod/api/spec/v2/model.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/api/swagger.go b/daemon/algod/api/swagger.go index 5402e802ec..601271fa55 100644 --- a/daemon/algod/api/swagger.go +++ b/daemon/algod/api/swagger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/deadlockLogger.go b/daemon/algod/deadlockLogger.go index 923db611f8..8498a3c416 100644 --- a/daemon/algod/deadlockLogger.go +++ b/daemon/algod/deadlockLogger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/deadlock_test.go b/daemon/algod/deadlock_test.go index 83cc118592..2e3b356c4c 100644 --- a/daemon/algod/deadlock_test.go +++ b/daemon/algod/deadlock_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 1b40e98bfb..57e1b443f5 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/algod/server_test.go b/daemon/algod/server_test.go index bb8ec350e7..459297ebd8 100644 --- a/daemon/algod/server_test.go +++ b/daemon/algod/server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/api.go b/daemon/kmd/api/api.go index 90c374482d..084b6f882b 100644 --- a/daemon/kmd/api/api.go +++ b/daemon/kmd/api/api.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/cors.go b/daemon/kmd/api/cors.go index 96c00b7ce6..6ff8e38453 100644 --- a/daemon/kmd/api/cors.go +++ b/daemon/kmd/api/cors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/v1/auth.go b/daemon/kmd/api/v1/auth.go index eb28cb9909..997d4326e3 100644 --- a/daemon/kmd/api/v1/auth.go +++ b/daemon/kmd/api/v1/auth.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/v1/errors.go b/daemon/kmd/api/v1/errors.go index 493551f640..762053438d 100644 --- a/daemon/kmd/api/v1/errors.go +++ b/daemon/kmd/api/v1/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/api/v1/handlers.go b/daemon/kmd/api/v1/handlers.go index f585cb274a..466daab4cf 100644 --- a/daemon/kmd/api/v1/handlers.go +++ b/daemon/kmd/api/v1/handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/client/client.go b/daemon/kmd/client/client.go index 339d970f6a..8d819557ea 100644 --- a/daemon/kmd/client/client.go +++ b/daemon/kmd/client/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/client/requests.go b/daemon/kmd/client/requests.go index dbd6cf69af..1bf13f7557 100644 --- a/daemon/kmd/client/requests.go +++ b/daemon/kmd/client/requests.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/client/wrappers.go b/daemon/kmd/client/wrappers.go index cb490c791e..a7b5613aff 100644 --- a/daemon/kmd/client/wrappers.go +++ b/daemon/kmd/client/wrappers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/config/config.go b/daemon/kmd/config/config.go index 3192c7a9a0..5dce6ba660 100644 --- a/daemon/kmd/config/config.go +++ b/daemon/kmd/config/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/config/errors.go b/daemon/kmd/config/errors.go index 510725b099..cd6197c4e7 100644 --- a/daemon/kmd/config/errors.go +++ b/daemon/kmd/config/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/kmd.go b/daemon/kmd/kmd.go index e0890c09b1..3d3ce3c92d 100644 --- a/daemon/kmd/kmd.go +++ b/daemon/kmd/kmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/lib/kmdapi/common.go b/daemon/kmd/lib/kmdapi/common.go index d3b05a115d..f1a5a624c5 100644 --- a/daemon/kmd/lib/kmdapi/common.go +++ b/daemon/kmd/lib/kmdapi/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/lib/kmdapi/requests.go b/daemon/kmd/lib/kmdapi/requests.go index c2d323c78c..445e88df18 100644 --- a/daemon/kmd/lib/kmdapi/requests.go +++ b/daemon/kmd/lib/kmdapi/requests.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/lib/kmdapi/responses.go b/daemon/kmd/lib/kmdapi/responses.go index 4d233e9cbd..783953795e 100644 --- a/daemon/kmd/lib/kmdapi/responses.go +++ b/daemon/kmd/lib/kmdapi/responses.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/server/errors.go b/daemon/kmd/server/errors.go index 9aac3feeb7..3b0f70ddac 100644 --- a/daemon/kmd/server/errors.go +++ b/daemon/kmd/server/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/server/server.go b/daemon/kmd/server/server.go index 0210791948..712583d47a 100644 --- a/daemon/kmd/server/server.go +++ b/daemon/kmd/server/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/session/auth.go b/daemon/kmd/session/auth.go index 29aa5acc20..57955a6115 100644 --- a/daemon/kmd/session/auth.go +++ b/daemon/kmd/session/auth.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/session/session.go b/daemon/kmd/session/session.go index 6aac936d9a..6ecdcf8268 100644 --- a/daemon/kmd/session/session.go +++ b/daemon/kmd/session/session.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/driver.go b/daemon/kmd/wallet/driver/driver.go index 9f1bb0c7fa..f22f4680b9 100644 --- a/daemon/kmd/wallet/driver/driver.go +++ b/daemon/kmd/wallet/driver/driver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/ledger.go b/daemon/kmd/wallet/driver/ledger.go index e26abe11d5..fbe5367ba3 100644 --- a/daemon/kmd/wallet/driver/ledger.go +++ b/daemon/kmd/wallet/driver/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/ledger_errors.go b/daemon/kmd/wallet/driver/ledger_errors.go index 0fd8811083..036b2df2cc 100644 --- a/daemon/kmd/wallet/driver/ledger_errors.go +++ b/daemon/kmd/wallet/driver/ledger_errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/ledger_hid.go b/daemon/kmd/wallet/driver/ledger_hid.go index 36140f9189..5a958f4a08 100644 --- a/daemon/kmd/wallet/driver/ledger_hid.go +++ b/daemon/kmd/wallet/driver/ledger_hid.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/sqlite.go b/daemon/kmd/wallet/driver/sqlite.go index 46992734db..d17e9dace1 100644 --- a/daemon/kmd/wallet/driver/sqlite.go +++ b/daemon/kmd/wallet/driver/sqlite.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/sqlite_crypto.go b/daemon/kmd/wallet/driver/sqlite_crypto.go index 2749f26838..8c77a9f844 100644 --- a/daemon/kmd/wallet/driver/sqlite_crypto.go +++ b/daemon/kmd/wallet/driver/sqlite_crypto.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/sqlite_errors.go b/daemon/kmd/wallet/driver/sqlite_errors.go index 39e122a4e7..a07fce2d86 100644 --- a/daemon/kmd/wallet/driver/sqlite_errors.go +++ b/daemon/kmd/wallet/driver/sqlite_errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/driver/util.go b/daemon/kmd/wallet/driver/util.go index 7319ae444d..533b8860c9 100644 --- a/daemon/kmd/wallet/driver/util.go +++ b/daemon/kmd/wallet/driver/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/daemon/kmd/wallet/wallet.go b/daemon/kmd/wallet/wallet.go index d4b6b5b1a6..a5b0fc0f71 100644 --- a/daemon/kmd/wallet/wallet.go +++ b/daemon/kmd/wallet/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/account.go b/data/account/account.go index 9c0d978c8f..0693d8afac 100644 --- a/data/account/account.go +++ b/data/account/account.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/partInstall.go b/data/account/partInstall.go index 0b10872785..0be175c79e 100644 --- a/data/account/partInstall.go +++ b/data/account/partInstall.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participation.go b/data/account/participation.go index 9493f21f0d..a4dcf04b74 100644 --- a/data/account/participation.go +++ b/data/account/participation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index 3add9ec5bd..0752bc73f0 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participationRegistryBench_test.go b/data/account/participationRegistryBench_test.go index 4c6f1098cd..70790ae748 100644 --- a/data/account/participationRegistryBench_test.go +++ b/data/account/participationRegistryBench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index e0328bb7f8..8cc5a4f111 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/participation_test.go b/data/account/participation_test.go index ccf300122e..41e1a2bf3d 100644 --- a/data/account/participation_test.go +++ b/data/account/participation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/registeryDbOps.go b/data/account/registeryDbOps.go index ad5174c1c8..62bd5772aa 100644 --- a/data/account/registeryDbOps.go +++ b/data/account/registeryDbOps.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/account/rootInstall.go b/data/account/rootInstall.go index 50f1c6e972..b070b8d9b3 100644 --- a/data/account/rootInstall.go +++ b/data/account/rootInstall.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/accountManager.go b/data/accountManager.go index 063ab4d1bd..8e10cb35c7 100644 --- a/data/accountManager.go +++ b/data/accountManager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/accountManager_test.go b/data/accountManager_test.go index 3d580a5f23..7a1fb2836f 100644 --- a/data/accountManager_test.go +++ b/data/accountManager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/appRateLimiter.go b/data/appRateLimiter.go index b7684409a8..1f4472e68a 100644 --- a/data/appRateLimiter.go +++ b/data/appRateLimiter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/appRateLimiter_test.go b/data/appRateLimiter_test.go index 2da9fddfa0..f5e63dfb36 100644 --- a/data/appRateLimiter_test.go +++ b/data/appRateLimiter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/address.go b/data/basics/address.go index 6e2249f4fb..10aeb821ee 100644 --- a/data/basics/address.go +++ b/data/basics/address.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/address_test.go b/data/basics/address_test.go index 264b90a565..129b453091 100644 --- a/data/basics/address_test.go +++ b/data/basics/address_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/fields_test.go b/data/basics/fields_test.go index 4baacdb167..a5a73edf7e 100644 --- a/data/basics/fields_test.go +++ b/data/basics/fields_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/overflow.go b/data/basics/overflow.go index d119ba3a9c..9f90577ec7 100644 --- a/data/basics/overflow.go +++ b/data/basics/overflow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/sort.go b/data/basics/sort.go index aa49863043..6e3614f383 100644 --- a/data/basics/sort.go +++ b/data/basics/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/stateProofParticipant.go b/data/basics/stateProofParticipant.go index 63f7849f85..2fa560793a 100644 --- a/data/basics/stateProofParticipant.go +++ b/data/basics/stateProofParticipant.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/teal.go b/data/basics/teal.go index 412de5902b..f3156ae4b8 100644 --- a/data/basics/teal.go +++ b/data/basics/teal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/teal_test.go b/data/basics/teal_test.go index 6703faeec3..f1f8e4e0b4 100644 --- a/data/basics/teal_test.go +++ b/data/basics/teal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/testing/userBalance.go b/data/basics/testing/userBalance.go index 13efdf5d58..dd0ebfde5d 100644 --- a/data/basics/testing/userBalance.go +++ b/data/basics/testing/userBalance.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/units.go b/data/basics/units.go index c1b8f413b2..af8743ee40 100644 --- a/data/basics/units.go +++ b/data/basics/units.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/units_test.go b/data/basics/units_test.go index c674270be2..3d80b23324 100644 --- a/data/basics/units_test.go +++ b/data/basics/units_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index e87517f28d..398147bb6d 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/basics/userBalance_test.go b/data/basics/userBalance_test.go index 912c75aed8..da094329fd 100644 --- a/data/basics/userBalance_test.go +++ b/data/basics/userBalance_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index 39842aa758..d67621b343 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go index a4cc3d6382..73cce6d7d4 100644 --- a/data/bookkeeping/block_test.go +++ b/data/bookkeeping/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/encoding_test.go b/data/bookkeeping/encoding_test.go index 95ccfbbca5..69326437bd 100644 --- a/data/bookkeeping/encoding_test.go +++ b/data/bookkeeping/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/genesis.go b/data/bookkeeping/genesis.go index a6e8eb6e50..8220d5232a 100644 --- a/data/bookkeeping/genesis.go +++ b/data/bookkeeping/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/genesis_test.go b/data/bookkeeping/genesis_test.go index 393e389229..360e0a9381 100644 --- a/data/bookkeeping/genesis_test.go +++ b/data/bookkeeping/genesis_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/lightBlockHeader.go b/data/bookkeeping/lightBlockHeader.go index 90edea3ba2..e9b0a111ab 100644 --- a/data/bookkeeping/lightBlockHeader.go +++ b/data/bookkeeping/lightBlockHeader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/lightBlockHeader_test.go b/data/bookkeeping/lightBlockHeader_test.go index 50d561c475..e61d03f6bb 100644 --- a/data/bookkeeping/lightBlockHeader_test.go +++ b/data/bookkeeping/lightBlockHeader_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/prettyprinting.go b/data/bookkeeping/prettyprinting.go index 96c259a813..7721c1f475 100644 --- a/data/bookkeeping/prettyprinting.go +++ b/data/bookkeeping/prettyprinting.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/txn_merkle.go b/data/bookkeeping/txn_merkle.go index 7ab84d0293..4aeec79f79 100644 --- a/data/bookkeeping/txn_merkle.go +++ b/data/bookkeeping/txn_merkle.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/bookkeeping/txn_merkle_test.go b/data/bookkeeping/txn_merkle_test.go index 22df9025a4..57d7fbc393 100644 --- a/data/bookkeeping/txn_merkle_test.go +++ b/data/bookkeeping/txn_merkle_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/committee.go b/data/committee/committee.go index 93703367e1..1432e32021 100644 --- a/data/committee/committee.go +++ b/data/committee/committee.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/common_test.go b/data/committee/common_test.go index 26a110a9c5..1f7e7bd373 100644 --- a/data/committee/common_test.go +++ b/data/committee/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/credential.go b/data/committee/credential.go index 6c6aac794e..79b22abad3 100644 --- a/data/committee/credential.go +++ b/data/committee/credential.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/credential_test.go b/data/committee/credential_test.go index 22dfcff83e..da2be625cd 100644 --- a/data/committee/credential_test.go +++ b/data/committee/credential_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/committee/encoding_test.go b/data/committee/encoding_test.go index be1a5ad9e5..1feaf1688c 100644 --- a/data/committee/encoding_test.go +++ b/data/committee/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/common_test.go b/data/common_test.go index 0ba58ad8d9..6079f4a226 100644 --- a/data/common_test.go +++ b/data/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/datatest/fabricateLedger.go b/data/datatest/fabricateLedger.go index 8bd1c3f251..9ad7bed4fc 100644 --- a/data/datatest/fabricateLedger.go +++ b/data/datatest/fabricateLedger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/datatest/impls.go b/data/datatest/impls.go index fefcb054da..7c9462d40d 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/hashable/message.go b/data/hashable/message.go index bfa032633f..fc8ae5e412 100644 --- a/data/hashable/message.go +++ b/data/hashable/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/ledger.go b/data/ledger.go index 141fae6cf8..fa3b958373 100644 --- a/data/ledger.go +++ b/data/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/ledger_test.go b/data/ledger_test.go index 452bdba158..caa866575d 100644 --- a/data/ledger_test.go +++ b/data/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/pools/errors.go b/data/pools/errors.go index 819678b2ca..5ce1a056e2 100644 --- a/data/pools/errors.go +++ b/data/pools/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/pools/statusCache.go b/data/pools/statusCache.go index 0b8d967417..ed11451933 100644 --- a/data/pools/statusCache.go +++ b/data/pools/statusCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index 47deae4d32..2eb787eafd 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index f03eadf7da..4229b0510d 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/stateproofmsg/message.go b/data/stateproofmsg/message.go index 5f1c2e3433..d87c5fc0e6 100644 --- a/data/stateproofmsg/message.go +++ b/data/stateproofmsg/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/application.go b/data/transactions/application.go index db7037700b..48a5788c04 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go index a0a839b132..2b5965a885 100644 --- a/data/transactions/application_test.go +++ b/data/transactions/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/asset.go b/data/transactions/asset.go index c15ec3db93..9a7cd7ecf4 100644 --- a/data/transactions/asset.go +++ b/data/transactions/asset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/common_test.go b/data/transactions/common_test.go index 9ad00816a5..388a1db5e4 100644 --- a/data/transactions/common_test.go +++ b/data/transactions/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/error.go b/data/transactions/error.go index c81af7a779..3012946d4f 100644 --- a/data/transactions/error.go +++ b/data/transactions/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/json_test.go b/data/transactions/json_test.go index 1e5474e54d..13462b48d9 100644 --- a/data/transactions/json_test.go +++ b/data/transactions/json_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/keyreg.go b/data/transactions/keyreg.go index 6e917ca115..b966f5c299 100644 --- a/data/transactions/keyreg.go +++ b/data/transactions/keyreg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 182b3e9dd3..a707e23f65 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index ffa7598c8f..e73129b432 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index 0ae1ba319e..fb11bc0b69 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/blackbox_test.go b/data/transactions/logic/blackbox_test.go index 55e7bde4f6..cdcf971a01 100644 --- a/data/transactions/logic/blackbox_test.go +++ b/data/transactions/logic/blackbox_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index 4938f95aa9..09d2f65018 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/box_test.go b/data/transactions/logic/box_test.go index 6649627440..61609d5a96 100644 --- a/data/transactions/logic/box_test.go +++ b/data/transactions/logic/box_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/crypto.go b/data/transactions/logic/crypto.go index 43fe4d50d5..feb20725fc 100644 --- a/data/transactions/logic/crypto.go +++ b/data/transactions/logic/crypto.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/crypto_test.go b/data/transactions/logic/crypto_test.go index 7c0dc5f582..7f539b8c7d 100644 --- a/data/transactions/logic/crypto_test.go +++ b/data/transactions/logic/crypto_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index e11b41b8a2..04478ba25a 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/debugger_eval_test.go b/data/transactions/logic/debugger_eval_test.go index d99452f103..b252e7f1a2 100644 --- a/data/transactions/logic/debugger_eval_test.go +++ b/data/transactions/logic/debugger_eval_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/debugger_test.go b/data/transactions/logic/debugger_test.go index 283bcaa6df..bd0f356d01 100644 --- a/data/transactions/logic/debugger_test.go +++ b/data/transactions/logic/debugger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index bfecb01a67..cfaadebdf3 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 9c9dad4ec3..21bfd156cb 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 971fffa72d..fe102086aa 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 4682732fb5..21ce259a7e 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/evalBench_test.go b/data/transactions/logic/evalBench_test.go index b9f61f0fa7..c9c748833e 100644 --- a/data/transactions/logic/evalBench_test.go +++ b/data/transactions/logic/evalBench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index d25851be0d..b0c6196e39 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 05e5786013..0eceac9a33 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 34ae9d9457..3ba93a87e6 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 060516d65f..a2f971f7b7 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 2fd432956a..7cec0b936f 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/frames.go b/data/transactions/logic/frames.go index 07a8bf665c..25fe68d29c 100644 --- a/data/transactions/logic/frames.go +++ b/data/transactions/logic/frames.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/frames_test.go b/data/transactions/logic/frames_test.go index 63ff1c21c3..80ce57544d 100644 --- a/data/transactions/logic/frames_test.go +++ b/data/transactions/logic/frames_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/jsonspec_test.go b/data/transactions/logic/jsonspec_test.go index a2bfcfff24..87a445138d 100644 --- a/data/transactions/logic/jsonspec_test.go +++ b/data/transactions/logic/jsonspec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index a575c493dd..6c2d62c7f0 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/mocktracer/scenarios.go b/data/transactions/logic/mocktracer/scenarios.go index e67907ea4d..09a6a3a2d0 100644 --- a/data/transactions/logic/mocktracer/scenarios.go +++ b/data/transactions/logic/mocktracer/scenarios.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index f40faf1be1..8ac31cb3a3 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/opcodeExplain.go b/data/transactions/logic/opcodeExplain.go index 3643ade21f..28f4f798b2 100644 --- a/data/transactions/logic/opcodeExplain.go +++ b/data/transactions/logic/opcodeExplain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 1f595dfb7c..650f26ed9b 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/opcodes_test.go b/data/transactions/logic/opcodes_test.go index df69bbf708..57a2d5eb76 100644 --- a/data/transactions/logic/opcodes_test.go +++ b/data/transactions/logic/opcodes_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/pairing.go b/data/transactions/logic/pairing.go index fa47e061d9..fc61996e66 100644 --- a/data/transactions/logic/pairing.go +++ b/data/transactions/logic/pairing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/pairing_test.go b/data/transactions/logic/pairing_test.go index 914e303006..d7b4a87e2f 100644 --- a/data/transactions/logic/pairing_test.go +++ b/data/transactions/logic/pairing_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/program.go b/data/transactions/logic/program.go index 85195b9043..dba9af446a 100644 --- a/data/transactions/logic/program.go +++ b/data/transactions/logic/program.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go index c407af7044..8c713da671 100644 --- a/data/transactions/logic/resources.go +++ b/data/transactions/logic/resources.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index b796a8c4c6..42ceee6d45 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/sourcemap.go b/data/transactions/logic/sourcemap.go index fb12f50f6a..4f5d2ab45d 100644 --- a/data/transactions/logic/sourcemap.go +++ b/data/transactions/logic/sourcemap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/sourcemap_test.go b/data/transactions/logic/sourcemap_test.go index f5c3455313..ba7be2974d 100644 --- a/data/transactions/logic/sourcemap_test.go +++ b/data/transactions/logic/sourcemap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/tracer.go b/data/transactions/logic/tracer.go index 37603c7944..c8996f316c 100644 --- a/data/transactions/logic/tracer.go +++ b/data/transactions/logic/tracer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go index 323447464b..8e806e21d9 100644 --- a/data/transactions/logic/tracer_test.go +++ b/data/transactions/logic/tracer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/logicsig.go b/data/transactions/logicsig.go index e2869a89b6..31a14ea1dc 100644 --- a/data/transactions/logicsig.go +++ b/data/transactions/logicsig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payment.go b/data/transactions/payment.go index fee9c395ee..62eafdb1e9 100644 --- a/data/transactions/payment.go +++ b/data/transactions/payment.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payment_test.go b/data/transactions/payment_test.go index 5d3f83cba7..e2583c429c 100644 --- a/data/transactions/payment_test.go +++ b/data/transactions/payment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payset.go b/data/transactions/payset.go index f005c6b4d9..7d70885906 100644 --- a/data/transactions/payset.go +++ b/data/transactions/payset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/payset_test.go b/data/transactions/payset_test.go index 63d3afc424..231e8efcae 100644 --- a/data/transactions/payset_test.go +++ b/data/transactions/payset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/perf_test.go b/data/transactions/perf_test.go index cda1ea5be8..9bcc83c0b8 100644 --- a/data/transactions/perf_test.go +++ b/data/transactions/perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/signedtxn.go b/data/transactions/signedtxn.go index 30d0e90e11..5a10829f09 100644 --- a/data/transactions/signedtxn.go +++ b/data/transactions/signedtxn.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/signedtxn_test.go b/data/transactions/signedtxn_test.go index d292e66890..f6862f2f4f 100644 --- a/data/transactions/signedtxn_test.go +++ b/data/transactions/signedtxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/sort.go b/data/transactions/sort.go index d86d6e2936..d8b3419996 100644 --- a/data/transactions/sort.go +++ b/data/transactions/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/stateproof.go b/data/transactions/stateproof.go index 89b14de752..7d24526851 100644 --- a/data/transactions/stateproof.go +++ b/data/transactions/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/teal.go b/data/transactions/teal.go index 85a162854c..bdb68b525d 100644 --- a/data/transactions/teal.go +++ b/data/transactions/teal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/teal_test.go b/data/transactions/teal_test.go index 52ec80e681..92dbe0330c 100644 --- a/data/transactions/teal_test.go +++ b/data/transactions/teal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/testhelpers.go b/data/transactions/testhelpers.go index 99baadb1a3..349e28cca3 100644 --- a/data/transactions/testhelpers.go +++ b/data/transactions/testhelpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 725f9d7ec2..b0bb7c413b 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 0f3cf18258..e3a25619bb 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/artifact_test.go b/data/transactions/verify/artifact_test.go index 1fa3d6b2d5..8e1cfcf68c 100644 --- a/data/transactions/verify/artifact_test.go +++ b/data/transactions/verify/artifact_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 3d42694bbe..640189cc57 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/txnBatch.go b/data/transactions/verify/txnBatch.go index 6a45944ed8..4b309f6946 100644 --- a/data/transactions/verify/txnBatch.go +++ b/data/transactions/verify/txnBatch.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/txnBatch_test.go b/data/transactions/verify/txnBatch_test.go index a7ee2df04a..45693f58a1 100644 --- a/data/transactions/verify/txnBatch_test.go +++ b/data/transactions/verify/txnBatch_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 1e7793248b..282a031097 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/verifiedTxnCache.go b/data/transactions/verify/verifiedTxnCache.go index 184ed273e6..cb73a43711 100644 --- a/data/transactions/verify/verifiedTxnCache.go +++ b/data/transactions/verify/verifiedTxnCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/transactions/verify/verifiedTxnCache_test.go b/data/transactions/verify/verifiedTxnCache_test.go index 880780f94d..d27510fe6a 100644 --- a/data/transactions/verify/verifiedTxnCache_test.go +++ b/data/transactions/verify/verifiedTxnCache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/txDupCache.go b/data/txDupCache.go index 96a426dc7b..ff0e04a866 100644 --- a/data/txDupCache.go +++ b/data/txDupCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/txDupCache_test.go b/data/txDupCache_test.go index 13a36b19d1..0458a3b76d 100644 --- a/data/txDupCache_test.go +++ b/data/txDupCache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/txHandler.go b/data/txHandler.go index 58febd1d76..3d20e95acd 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 74282b5c22..3f567554bf 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/txntest/defi.go b/data/txntest/defi.go index 635393a675..fedadd00d6 100644 --- a/data/txntest/defi.go +++ b/data/txntest/defi.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/data/txntest/txn.go b/data/txntest/txn.go index 5815aacc60..515c9df458 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/gen/generate.go b/gen/generate.go index 5fb1c0126a..a5f6e46800 100644 --- a/gen/generate.go +++ b/gen/generate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/gen/generate_test.go b/gen/generate_test.go index e154e7c44d..d4c2ab152d 100644 --- a/gen/generate_test.go +++ b/gen/generate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/gen/walletData.go b/gen/walletData.go index 9823c03b57..2c8f678ce0 100644 --- a/gen/walletData.go +++ b/gen/walletData.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/internal/rapidgen/rapidgenerators.go b/internal/rapidgen/rapidgenerators.go index 37b9b69881..ba454d6d26 100644 --- a/internal/rapidgen/rapidgenerators.go +++ b/internal/rapidgen/rapidgenerators.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/internal/rapidgen/tld.go b/internal/rapidgen/tld.go index e6e3e8963a..7d93ead420 100644 --- a/internal/rapidgen/tld.go +++ b/internal/rapidgen/tld.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctdeltas.go b/ledger/acctdeltas.go index 42d5d93b2f..d6bb91881f 100644 --- a/ledger/acctdeltas.go +++ b/ledger/acctdeltas.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index bf4bd6e399..ec00269dbe 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctonline.go b/ledger/acctonline.go index e2760ee0a2..cdccc9a9e1 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctonline_expired_test.go b/ledger/acctonline_expired_test.go index bfc72fa981..ac7986a78b 100644 --- a/ledger/acctonline_expired_test.go +++ b/ledger/acctonline_expired_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index 2897e87ffe..5a2b2ca1ac 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index ca9cd55f72..e9c9696711 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 1f3ca086a2..1cf080d15e 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/applications_test.go b/ledger/applications_test.go index d89f03a67f..315cfd2c47 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/application.go b/ledger/apply/application.go index c87368dfe1..33ae9545fb 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index 5d829a386b..dfc298bf84 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/apply.go b/ledger/apply/apply.go index 2be394b8ff..dfa61b2632 100644 --- a/ledger/apply/apply.go +++ b/ledger/apply/apply.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/asset.go b/ledger/apply/asset.go index f0701d28e9..186b82ad46 100644 --- a/ledger/apply/asset.go +++ b/ledger/apply/asset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/asset_test.go b/ledger/apply/asset_test.go index 802c692c82..eddc15757e 100644 --- a/ledger/apply/asset_test.go +++ b/ledger/apply/asset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/keyreg.go b/ledger/apply/keyreg.go index 206ff31bf3..4fe0f0a326 100644 --- a/ledger/apply/keyreg.go +++ b/ledger/apply/keyreg.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/keyreg_test.go b/ledger/apply/keyreg_test.go index 7589b9fb1f..e64beea4b4 100644 --- a/ledger/apply/keyreg_test.go +++ b/ledger/apply/keyreg_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/mockBalances_test.go b/ledger/apply/mockBalances_test.go index 3d441b03c8..43af5fa11d 100644 --- a/ledger/apply/mockBalances_test.go +++ b/ledger/apply/mockBalances_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/payment.go b/ledger/apply/payment.go index ad8f128e95..8483bae92b 100644 --- a/ledger/apply/payment.go +++ b/ledger/apply/payment.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/payment_test.go b/ledger/apply/payment_test.go index db82869e23..d377211a17 100644 --- a/ledger/apply/payment_test.go +++ b/ledger/apply/payment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/stateproof.go b/ledger/apply/stateproof.go index 2901f4e857..70db26e882 100644 --- a/ledger/apply/stateproof.go +++ b/ledger/apply/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apply/stateproof_test.go b/ledger/apply/stateproof_test.go index 155a4eef5d..be774d2e4f 100644 --- a/ledger/apply/stateproof_test.go +++ b/ledger/apply/stateproof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 982c9f01b2..167d22f2dc 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/archival_test.go b/ledger/archival_test.go index 9b361b7036..96477708f5 100644 --- a/ledger/archival_test.go +++ b/ledger/archival_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/blockqueue.go b/ledger/blockqueue.go index a72c1d8cba..686d51b96f 100644 --- a/ledger/blockqueue.go +++ b/ledger/blockqueue.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/blockqueue_test.go b/ledger/blockqueue_test.go index 2d702cfb90..8b451a95ed 100644 --- a/ledger/blockqueue_test.go +++ b/ledger/blockqueue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index 3a66583d83..7f42beb143 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/bulletin.go b/ledger/bulletin.go index 5dc2b99aaa..0b3f08a6b4 100644 --- a/ledger/bulletin.go +++ b/ledger/bulletin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/bulletin_test.go b/ledger/bulletin_test.go index 88d3784470..69bc2525d8 100644 --- a/ledger/bulletin_test.go +++ b/ledger/bulletin_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchpointfileheader.go b/ledger/catchpointfileheader.go index 21cd6dbd59..f076f7267c 100644 --- a/ledger/catchpointfileheader.go +++ b/ledger/catchpointfileheader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchpointfilewriter.go b/ledger/catchpointfilewriter.go index 140223c3ff..cd58606a2f 100644 --- a/ledger/catchpointfilewriter.go +++ b/ledger/catchpointfilewriter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchpointfilewriter_test.go b/ledger/catchpointfilewriter_test.go index f378f633a3..71a0737fb6 100644 --- a/ledger/catchpointfilewriter_test.go +++ b/ledger/catchpointfilewriter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index 7745e33f55..b50a0d2d1e 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index a3a4509330..60f7366c74 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchupaccessor.go b/ledger/catchupaccessor.go index 3661c60057..315fa6b003 100644 --- a/ledger/catchupaccessor.go +++ b/ledger/catchupaccessor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/catchupaccessor_test.go b/ledger/catchupaccessor_test.go index 63b0fa2420..37f27c6794 100644 --- a/ledger/catchupaccessor_test.go +++ b/ledger/catchupaccessor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/double_test.go b/ledger/double_test.go index 16d38e7681..c08212e373 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/encoded/recordsV5.go b/ledger/encoded/recordsV5.go index 9af95207c6..86f1373979 100644 --- a/ledger/encoded/recordsV5.go +++ b/ledger/encoded/recordsV5.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/encoded/recordsV6.go b/ledger/encoded/recordsV6.go index 6d0616c80b..520f6f2b8e 100644 --- a/ledger/encoded/recordsV6.go +++ b/ledger/encoded/recordsV6.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/encoded/recordsV6_test.go b/ledger/encoded/recordsV6_test.go index 9e5dd14384..84c43e6c82 100644 --- a/ledger/encoded/recordsV6_test.go +++ b/ledger/encoded/recordsV6_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/appcow.go b/ledger/eval/appcow.go index 14815e8e2c..ff1c0cc23a 100644 --- a/ledger/eval/appcow.go +++ b/ledger/eval/appcow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/appcow_test.go b/ledger/eval/appcow_test.go index a4c8d22c67..60ab1df25e 100644 --- a/ledger/eval/appcow_test.go +++ b/ledger/eval/appcow_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 5419bc9525..8be898e05c 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/assetcow.go b/ledger/eval/assetcow.go index 50b7106757..6e498d70bd 100644 --- a/ledger/eval/assetcow.go +++ b/ledger/eval/assetcow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index f3e6b3d049..269ce570ff 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/cow_creatables.go b/ledger/eval/cow_creatables.go index 3159d230b5..00d90ab5b3 100644 --- a/ledger/eval/cow_creatables.go +++ b/ledger/eval/cow_creatables.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index 5e1253adce..06d600d58a 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 714e6bddf4..de1304988a 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index cb6bf08784..7884a7f894 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/evalindexer.go b/ledger/eval/evalindexer.go index af3a5546c6..f1185df6fd 100644 --- a/ledger/eval/evalindexer.go +++ b/ledger/eval/evalindexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/prefetcher/error.go b/ledger/eval/prefetcher/error.go index 77c1cb999c..9c6b8edaf3 100644 --- a/ledger/eval/prefetcher/error.go +++ b/ledger/eval/prefetcher/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/prefetcher/prefetcher.go b/ledger/eval/prefetcher/prefetcher.go index 2187fa1b5c..487d370524 100644 --- a/ledger/eval/prefetcher/prefetcher.go +++ b/ledger/eval/prefetcher/prefetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go index 81ce168d43..57f4c5d92b 100644 --- a/ledger/eval/prefetcher/prefetcher_alignment_test.go +++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/prefetcher/prefetcher_test.go b/ledger/eval/prefetcher/prefetcher_test.go index 2eff5f3449..1ec96ad3f7 100644 --- a/ledger/eval/prefetcher/prefetcher_test.go +++ b/ledger/eval/prefetcher/prefetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/prefetcher/prefetcher_whitebox_test.go b/ledger/eval/prefetcher/prefetcher_whitebox_test.go index 6a8738b48c..13dfceed83 100644 --- a/ledger/eval/prefetcher/prefetcher_whitebox_test.go +++ b/ledger/eval/prefetcher/prefetcher_whitebox_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/txntracer.go b/ledger/eval/txntracer.go index f4c6277c71..8736a1fdb9 100644 --- a/ledger/eval/txntracer.go +++ b/ledger/eval/txntracer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval/txntracer_test.go b/ledger/eval/txntracer_test.go index 4711709c68..6875c99c38 100644 --- a/ledger/eval/txntracer_test.go +++ b/ledger/eval/txntracer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index 606ee4931d..7354b4a567 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/evalbench_test.go b/ledger/evalbench_test.go index b0c3b6780f..371cdad87d 100644 --- a/ledger/evalbench_test.go +++ b/ledger/evalbench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go index 1caa7d8ef5..f83dbb9809 100644 --- a/ledger/evalindexer.go +++ b/ledger/evalindexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/evalindexer_test.go b/ledger/evalindexer_test.go index 8209bb9282..c85d5f0275 100644 --- a/ledger/evalindexer_test.go +++ b/ledger/evalindexer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/fullblock_perf_test.go b/ledger/fullblock_perf_test.go index cad634a5a7..c45963cb1c 100644 --- a/ledger/fullblock_perf_test.go +++ b/ledger/fullblock_perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledger.go b/ledger/ledger.go index 458ce3d532..9313f55e68 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledger_perf_test.go b/ledger/ledger_perf_test.go index ba76df5e78..f160838ab1 100644 --- a/ledger/ledger_perf_test.go +++ b/ledger/ledger_perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 172643d48c..79ae16284d 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/accountdata.go b/ledger/ledgercore/accountdata.go index 69e2c543eb..3685d16909 100644 --- a/ledger/ledgercore/accountdata.go +++ b/ledger/ledgercore/accountdata.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/accountdata_test.go b/ledger/ledgercore/accountdata_test.go index 76eb82d0d3..fd345eff67 100644 --- a/ledger/ledgercore/accountdata_test.go +++ b/ledger/ledgercore/accountdata_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/accountresource.go b/ledger/ledgercore/accountresource.go index 4d22ec0dc1..e1b13e71d7 100644 --- a/ledger/ledgercore/accountresource.go +++ b/ledger/ledgercore/accountresource.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/catchpointlabel.go b/ledger/ledgercore/catchpointlabel.go index 6ed5cfd277..b80a0bc1e2 100644 --- a/ledger/ledgercore/catchpointlabel.go +++ b/ledger/ledgercore/catchpointlabel.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/catchpointlabel_test.go b/ledger/ledgercore/catchpointlabel_test.go index d2b0ad41f0..d5c8a9b0e7 100644 --- a/ledger/ledgercore/catchpointlabel_test.go +++ b/ledger/ledgercore/catchpointlabel_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/error.go b/ledger/ledgercore/error.go index f5d347d6fb..e2084584b7 100644 --- a/ledger/ledgercore/error.go +++ b/ledger/ledgercore/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/misc.go b/ledger/ledgercore/misc.go index 86fc2c3ff9..c0e72da433 100644 --- a/ledger/ledgercore/misc.go +++ b/ledger/ledgercore/misc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/onlineacct.go b/ledger/ledgercore/onlineacct.go index 690ebcfe52..8a6b771aad 100644 --- a/ledger/ledgercore/onlineacct.go +++ b/ledger/ledgercore/onlineacct.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index a730af251a..b735d391fe 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/statedelta_test.go b/ledger/ledgercore/statedelta_test.go index 2e683a410d..1e2efabe08 100644 --- a/ledger/ledgercore/statedelta_test.go +++ b/ledger/ledgercore/statedelta_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/stateproofverification.go b/ledger/ledgercore/stateproofverification.go index 0220185a20..19d1134f70 100644 --- a/ledger/ledgercore/stateproofverification.go +++ b/ledger/ledgercore/stateproofverification.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/totals.go b/ledger/ledgercore/totals.go index 904cac9114..22708586b1 100644 --- a/ledger/ledgercore/totals.go +++ b/ledger/ledgercore/totals.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/totals_test.go b/ledger/ledgercore/totals_test.go index 66f4e09ac3..b9353ba6b3 100644 --- a/ledger/ledgercore/totals_test.go +++ b/ledger/ledgercore/totals_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/validatedBlock.go b/ledger/ledgercore/validatedBlock.go index 84d09e2a9a..541a3a54f8 100644 --- a/ledger/ledgercore/validatedBlock.go +++ b/ledger/ledgercore/validatedBlock.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/ledgercore/votersForRound.go b/ledger/ledgercore/votersForRound.go index 4cce400bc3..7ab103dcd1 100644 --- a/ledger/ledgercore/votersForRound.go +++ b/ledger/ledgercore/votersForRound.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruaccts.go b/ledger/lruaccts.go index 7d3b601454..6b127c0604 100644 --- a/ledger/lruaccts.go +++ b/ledger/lruaccts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruaccts_test.go b/ledger/lruaccts_test.go index 84674fc627..4ddca0d4d6 100644 --- a/ledger/lruaccts_test.go +++ b/ledger/lruaccts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lrukv.go b/ledger/lrukv.go index ef283faec6..cc64c6f662 100644 --- a/ledger/lrukv.go +++ b/ledger/lrukv.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lrukv_test.go b/ledger/lrukv_test.go index 35345091b2..70b49cef0e 100644 --- a/ledger/lrukv_test.go +++ b/ledger/lrukv_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruonlineaccts.go b/ledger/lruonlineaccts.go index 297fb03328..e96eb9212c 100644 --- a/ledger/lruonlineaccts.go +++ b/ledger/lruonlineaccts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruonlineaccts_test.go b/ledger/lruonlineaccts_test.go index ae1a8a7736..0f3bc81712 100644 --- a/ledger/lruonlineaccts_test.go +++ b/ledger/lruonlineaccts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruresources.go b/ledger/lruresources.go index 6a0367b7af..58d66d3600 100644 --- a/ledger/lruresources.go +++ b/ledger/lruresources.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/lruresources_test.go b/ledger/lruresources_test.go index b678ea742b..6d31938837 100644 --- a/ledger/lruresources_test.go +++ b/ledger/lruresources_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/metrics.go b/ledger/metrics.go index 017960d907..7a56d58d56 100644 --- a/ledger/metrics.go +++ b/ledger/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/metrics_test.go b/ledger/metrics_test.go index bca0b6bcc0..3f9eed305c 100644 --- a/ledger/metrics_test.go +++ b/ledger/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/notifier.go b/ledger/notifier.go index c9ea45e41a..aabf62d080 100644 --- a/ledger/notifier.go +++ b/ledger/notifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/onlineaccountscache.go b/ledger/onlineaccountscache.go index 8340d2d4d5..e6145a4e43 100644 --- a/ledger/onlineaccountscache.go +++ b/ledger/onlineaccountscache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/onlineaccountscache_test.go b/ledger/onlineaccountscache_test.go index 0d4de85fc6..b64d18aabf 100644 --- a/ledger/onlineaccountscache_test.go +++ b/ledger/onlineaccountscache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/onlinetopheap.go b/ledger/onlinetopheap.go index 6d3ba155d9..412f361544 100644 --- a/ledger/onlinetopheap.go +++ b/ledger/onlinetopheap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/onlinetopheap_test.go b/ledger/onlinetopheap_test.go index 10a068aa97..69fbd5da3a 100644 --- a/ledger/onlinetopheap_test.go +++ b/ledger/onlinetopheap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/perf_test.go b/ledger/perf_test.go index 68e9ab76c9..ffeb82cf03 100644 --- a/ledger/perf_test.go +++ b/ledger/perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simple_test.go b/ledger/simple_test.go index 1f4c612902..10b87f9378 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/initialStates.go b/ledger/simulation/initialStates.go index e374719ccc..079a38fa60 100644 --- a/ledger/simulation/initialStates.go +++ b/ledger/simulation/initialStates.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/resources.go b/ledger/simulation/resources.go index ff4d907273..3b4546cd28 100644 --- a/ledger/simulation/resources.go +++ b/ledger/simulation/resources.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/resources_test.go b/ledger/simulation/resources_test.go index 8afcadfa6e..b154031f57 100644 --- a/ledger/simulation/resources_test.go +++ b/ledger/simulation/resources_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 7d2bf83809..100ea8b7f1 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index be9f29556b..0a33e5f12b 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 5c9d2ad8e4..95cafe12e9 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index d141058106..ae43fe72fc 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 99ac9b416e..09d51f3193 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index e590250c94..b867cf6aac 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/simulation/tracer_test.go b/ledger/simulation/tracer_test.go index 190739e73c..b62f52fefa 100644 --- a/ledger/simulation/tracer_test.go +++ b/ledger/simulation/tracer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/spverificationtracker.go b/ledger/spverificationtracker.go index c34825a9c5..b430368981 100644 --- a/ledger/spverificationtracker.go +++ b/ledger/spverificationtracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/spverificationtracker_test.go b/ledger/spverificationtracker_test.go index 47d85bab31..7bba5eac87 100644 --- a/ledger/spverificationtracker_test.go +++ b/ledger/spverificationtracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/blockdb/blockdb.go b/ledger/store/blockdb/blockdb.go index bbd11d6c1e..9b0c80dce2 100644 --- a/ledger/store/blockdb/blockdb.go +++ b/ledger/store/blockdb/blockdb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/blockdb/blockdb_test.go b/ledger/store/blockdb/blockdb_test.go index 858e561e48..149c1a3dd3 100644 --- a/ledger/store/blockdb/blockdb_test.go +++ b/ledger/store/blockdb/blockdb_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/testing/helpers.go b/ledger/store/testing/helpers.go index b9e41ee286..e03b09b85d 100644 --- a/ledger/store/testing/helpers.go +++ b/ledger/store/testing/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/catchpoint.go b/ledger/store/trackerdb/catchpoint.go index 7c67155d45..ad6c9a236d 100644 --- a/ledger/store/trackerdb/catchpoint.go +++ b/ledger/store/trackerdb/catchpoint.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/data.go b/ledger/store/trackerdb/data.go index 264c055821..fc243b2b92 100644 --- a/ledger/store/trackerdb/data.go +++ b/ledger/store/trackerdb/data.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/data_test.go b/ledger/store/trackerdb/data_test.go index fe253304fd..e329a84e74 100644 --- a/ledger/store/trackerdb/data_test.go +++ b/ledger/store/trackerdb/data_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/accounts_reader.go b/ledger/store/trackerdb/dualdriver/accounts_reader.go index 1ceb8a5a24..b39aadd7cb 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_reader.go +++ b/ledger/store/trackerdb/dualdriver/accounts_reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go b/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go index 4fd389185e..6f241d3e5e 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go +++ b/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/accounts_writer.go b/ledger/store/trackerdb/dualdriver/accounts_writer.go index 318ccd36c0..586ce96978 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_writer.go +++ b/ledger/store/trackerdb/dualdriver/accounts_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go b/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go index 27b74381f0..b30e2fd79c 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go +++ b/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/dualdriver.go b/ledger/store/trackerdb/dualdriver/dualdriver.go index 505a100bbb..c7763dacef 100644 --- a/ledger/store/trackerdb/dualdriver/dualdriver.go +++ b/ledger/store/trackerdb/dualdriver/dualdriver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/online_accounts_reader.go b/ledger/store/trackerdb/dualdriver/online_accounts_reader.go index 60fec84ddb..f67689c024 100644 --- a/ledger/store/trackerdb/dualdriver/online_accounts_reader.go +++ b/ledger/store/trackerdb/dualdriver/online_accounts_reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/online_accounts_writer.go b/ledger/store/trackerdb/dualdriver/online_accounts_writer.go index 823ed54eee..97a03062f8 100644 --- a/ledger/store/trackerdb/dualdriver/online_accounts_writer.go +++ b/ledger/store/trackerdb/dualdriver/online_accounts_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/stateproof_reader.go b/ledger/store/trackerdb/dualdriver/stateproof_reader.go index 1ed61b4ebc..cdb4093bb8 100644 --- a/ledger/store/trackerdb/dualdriver/stateproof_reader.go +++ b/ledger/store/trackerdb/dualdriver/stateproof_reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/stateproof_writer.go b/ledger/store/trackerdb/dualdriver/stateproof_writer.go index 208ce3cbd4..25ca52da34 100644 --- a/ledger/store/trackerdb/dualdriver/stateproof_writer.go +++ b/ledger/store/trackerdb/dualdriver/stateproof_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/dualdriver/transaction_for_testing.go b/ledger/store/trackerdb/dualdriver/transaction_for_testing.go index 18ba888a52..a6946e7908 100644 --- a/ledger/store/trackerdb/dualdriver/transaction_for_testing.go +++ b/ledger/store/trackerdb/dualdriver/transaction_for_testing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/accounts_ext_reader.go b/ledger/store/trackerdb/generickv/accounts_ext_reader.go index 132149da6f..af2730a514 100644 --- a/ledger/store/trackerdb/generickv/accounts_ext_reader.go +++ b/ledger/store/trackerdb/generickv/accounts_ext_reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/accounts_ext_writer.go b/ledger/store/trackerdb/generickv/accounts_ext_writer.go index c135f73c59..b50a5363f1 100644 --- a/ledger/store/trackerdb/generickv/accounts_ext_writer.go +++ b/ledger/store/trackerdb/generickv/accounts_ext_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/accounts_reader.go b/ledger/store/trackerdb/generickv/accounts_reader.go index 7cbdc93f1f..063b5a35f5 100644 --- a/ledger/store/trackerdb/generickv/accounts_reader.go +++ b/ledger/store/trackerdb/generickv/accounts_reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/accounts_writer.go b/ledger/store/trackerdb/generickv/accounts_writer.go index 42530e7681..590ba4fcfd 100644 --- a/ledger/store/trackerdb/generickv/accounts_writer.go +++ b/ledger/store/trackerdb/generickv/accounts_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/catchpoint.go b/ledger/store/trackerdb/generickv/catchpoint.go index 99c8a7d955..8a21bb5452 100644 --- a/ledger/store/trackerdb/generickv/catchpoint.go +++ b/ledger/store/trackerdb/generickv/catchpoint.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/init_accounts.go b/ledger/store/trackerdb/generickv/init_accounts.go index af587af105..39f6f25247 100644 --- a/ledger/store/trackerdb/generickv/init_accounts.go +++ b/ledger/store/trackerdb/generickv/init_accounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/migrations.go b/ledger/store/trackerdb/generickv/migrations.go index e3d04ba5cf..c2a0d0f208 100644 --- a/ledger/store/trackerdb/generickv/migrations.go +++ b/ledger/store/trackerdb/generickv/migrations.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/onlineaccounts_reader.go b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go index 573bf478f5..0b72cbc5df 100644 --- a/ledger/store/trackerdb/generickv/onlineaccounts_reader.go +++ b/ledger/store/trackerdb/generickv/onlineaccounts_reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/onlineaccounts_writer.go b/ledger/store/trackerdb/generickv/onlineaccounts_writer.go index 054d75e87f..83a194022f 100644 --- a/ledger/store/trackerdb/generickv/onlineaccounts_writer.go +++ b/ledger/store/trackerdb/generickv/onlineaccounts_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/reader.go b/ledger/store/trackerdb/generickv/reader.go index 1532f2fca6..454a4eb9dc 100644 --- a/ledger/store/trackerdb/generickv/reader.go +++ b/ledger/store/trackerdb/generickv/reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/schema.go b/ledger/store/trackerdb/generickv/schema.go index 0c3abf8943..47168a1288 100644 --- a/ledger/store/trackerdb/generickv/schema.go +++ b/ledger/store/trackerdb/generickv/schema.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/stateproof_reader.go b/ledger/store/trackerdb/generickv/stateproof_reader.go index 18bce01c4a..5cd96c44e7 100644 --- a/ledger/store/trackerdb/generickv/stateproof_reader.go +++ b/ledger/store/trackerdb/generickv/stateproof_reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/stateproof_writer.go b/ledger/store/trackerdb/generickv/stateproof_writer.go index 9aa7145185..8ef45b1b74 100644 --- a/ledger/store/trackerdb/generickv/stateproof_writer.go +++ b/ledger/store/trackerdb/generickv/stateproof_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/generickv/writer.go b/ledger/store/trackerdb/generickv/writer.go index c5d8dcfc0f..8975c97c6e 100644 --- a/ledger/store/trackerdb/generickv/writer.go +++ b/ledger/store/trackerdb/generickv/writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/hashing.go b/ledger/store/trackerdb/hashing.go index 7a866087cf..4a0122436f 100644 --- a/ledger/store/trackerdb/hashing.go +++ b/ledger/store/trackerdb/hashing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/interface.go b/ledger/store/trackerdb/interface.go index 62203e5957..8d88f0d42a 100644 --- a/ledger/store/trackerdb/interface.go +++ b/ledger/store/trackerdb/interface.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/params.go b/ledger/store/trackerdb/params.go index 64d5be9313..276bf19bd5 100644 --- a/ledger/store/trackerdb/params.go +++ b/ledger/store/trackerdb/params.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go b/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go index 514dc0ee58..7d1b394015 100644 --- a/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go +++ b/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/pebbledbdriver/pebbledriver_arm.go b/ledger/store/trackerdb/pebbledbdriver/pebbledriver_arm.go index cd17555aea..205ab07e64 100644 --- a/ledger/store/trackerdb/pebbledbdriver/pebbledriver_arm.go +++ b/ledger/store/trackerdb/pebbledbdriver/pebbledriver_arm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/pebbledbdriver/testing.go b/ledger/store/trackerdb/pebbledbdriver/testing.go index 2724eb01fb..c433b4df85 100644 --- a/ledger/store/trackerdb/pebbledbdriver/testing.go +++ b/ledger/store/trackerdb/pebbledbdriver/testing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/accountsV2.go b/ledger/store/trackerdb/sqlitedriver/accountsV2.go index ec48265fa1..bb20285d9d 100644 --- a/ledger/store/trackerdb/sqlitedriver/accountsV2.go +++ b/ledger/store/trackerdb/sqlitedriver/accountsV2.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go b/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go index 1e4a2217f5..4aa508e9b3 100644 --- a/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go +++ b/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/catchpoint.go b/ledger/store/trackerdb/sqlitedriver/catchpoint.go index ef63b7a7f8..cdda10978e 100644 --- a/ledger/store/trackerdb/sqlitedriver/catchpoint.go +++ b/ledger/store/trackerdb/sqlitedriver/catchpoint.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/catchpointPendingHashesIter.go b/ledger/store/trackerdb/sqlitedriver/catchpointPendingHashesIter.go index 611c67c877..2fdf13eb08 100644 --- a/ledger/store/trackerdb/sqlitedriver/catchpointPendingHashesIter.go +++ b/ledger/store/trackerdb/sqlitedriver/catchpointPendingHashesIter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go b/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go index e2c57c0f01..72af28e39e 100644 --- a/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go +++ b/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go b/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go index 6dc3b67226..7d2829d3e2 100644 --- a/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go +++ b/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/kvsIter.go b/ledger/store/trackerdb/sqlitedriver/kvsIter.go index 3beb6ae0d6..4ae08962a0 100644 --- a/ledger/store/trackerdb/sqlitedriver/kvsIter.go +++ b/ledger/store/trackerdb/sqlitedriver/kvsIter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/merkle_committer.go b/ledger/store/trackerdb/sqlitedriver/merkle_committer.go index 052dfdac93..25425d08e2 100644 --- a/ledger/store/trackerdb/sqlitedriver/merkle_committer.go +++ b/ledger/store/trackerdb/sqlitedriver/merkle_committer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/orderedAccountsIter.go b/ledger/store/trackerdb/sqlitedriver/orderedAccountsIter.go index 2752001131..4b021e5aea 100644 --- a/ledger/store/trackerdb/sqlitedriver/orderedAccountsIter.go +++ b/ledger/store/trackerdb/sqlitedriver/orderedAccountsIter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/schema.go b/ledger/store/trackerdb/sqlitedriver/schema.go index ea6f577868..369ff264e0 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema.go +++ b/ledger/store/trackerdb/sqlitedriver/schema.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/schema_test.go b/ledger/store/trackerdb/sqlitedriver/schema_test.go index 581082447b..e7aee0b2a9 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema_test.go +++ b/ledger/store/trackerdb/sqlitedriver/schema_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go b/ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go index 0354a3c832..b5eb3f0cc3 100644 --- a/ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go +++ b/ledger/store/trackerdb/sqlitedriver/spVerificationAccessor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/sql.go b/ledger/store/trackerdb/sqlitedriver/sql.go index 0621dc1754..634c7f54ef 100644 --- a/ledger/store/trackerdb/sqlitedriver/sql.go +++ b/ledger/store/trackerdb/sqlitedriver/sql.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/sql_test.go b/ledger/store/trackerdb/sqlitedriver/sql_test.go index 65e4782b5d..86333b96c7 100644 --- a/ledger/store/trackerdb/sqlitedriver/sql_test.go +++ b/ledger/store/trackerdb/sqlitedriver/sql_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go index 02c290be2d..07459fa0c0 100644 --- a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go +++ b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/testing.go b/ledger/store/trackerdb/sqlitedriver/testing.go index 4fed5e4723..49736a681a 100644 --- a/ledger/store/trackerdb/sqlitedriver/testing.go +++ b/ledger/store/trackerdb/sqlitedriver/testing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go index 0f59a5ce57..db1416b521 100644 --- a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go +++ b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/store.go b/ledger/store/trackerdb/store.go index 4140f5be14..735550ed96 100644 --- a/ledger/store/trackerdb/store.go +++ b/ledger/store/trackerdb/store.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testdb/testdb.go b/ledger/store/trackerdb/testdb/testdb.go index d5b524ade3..05acf777cc 100644 --- a/ledger/store/trackerdb/testdb/testdb.go +++ b/ledger/store/trackerdb/testdb/testdb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testinterface.go b/ledger/store/trackerdb/testinterface.go index a406375a2f..a67cc79e40 100644 --- a/ledger/store/trackerdb/testinterface.go +++ b/ledger/store/trackerdb/testinterface.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go index aebabd781f..227a80d5f8 100644 --- a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go +++ b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/accounts_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_kv_test.go index 1ca7b742a6..115e958901 100644 --- a/ledger/store/trackerdb/testsuite/accounts_kv_test.go +++ b/ledger/store/trackerdb/testsuite/accounts_kv_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/dbsemantics_test.go b/ledger/store/trackerdb/testsuite/dbsemantics_test.go index b155b918a9..5c711ffb51 100644 --- a/ledger/store/trackerdb/testsuite/dbsemantics_test.go +++ b/ledger/store/trackerdb/testsuite/dbsemantics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/dual_test.go b/ledger/store/trackerdb/testsuite/dual_test.go index 1b1cc61ec1..a8fe7e4b31 100644 --- a/ledger/store/trackerdb/testsuite/dual_test.go +++ b/ledger/store/trackerdb/testsuite/dual_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/migration_test.go b/ledger/store/trackerdb/testsuite/migration_test.go index 10d9c74737..d6b14ebdb2 100644 --- a/ledger/store/trackerdb/testsuite/migration_test.go +++ b/ledger/store/trackerdb/testsuite/migration_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/mockdb_test.go b/ledger/store/trackerdb/testsuite/mockdb_test.go index cc9397f2b4..8d1ac0ea86 100644 --- a/ledger/store/trackerdb/testsuite/mockdb_test.go +++ b/ledger/store/trackerdb/testsuite/mockdb_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go index f4064658c3..f71f4ae1da 100644 --- a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go +++ b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/pebbledb_test.go b/ledger/store/trackerdb/testsuite/pebbledb_test.go index 56f614b7e9..eecb1d555f 100644 --- a/ledger/store/trackerdb/testsuite/pebbledb_test.go +++ b/ledger/store/trackerdb/testsuite/pebbledb_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/sqlitedb_test.go b/ledger/store/trackerdb/testsuite/sqlitedb_test.go index c4a3d1a7b8..5a0d942ab8 100644 --- a/ledger/store/trackerdb/testsuite/sqlitedb_test.go +++ b/ledger/store/trackerdb/testsuite/sqlitedb_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go index bc42141735..e9341a6534 100644 --- a/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go +++ b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/testsuite/utils_test.go b/ledger/store/trackerdb/testsuite/utils_test.go index 7862155d0b..826402a357 100644 --- a/ledger/store/trackerdb/testsuite/utils_test.go +++ b/ledger/store/trackerdb/testsuite/utils_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/utils.go b/ledger/store/trackerdb/utils.go index 0d9e8b4d8a..96c562a560 100644 --- a/ledger/store/trackerdb/utils.go +++ b/ledger/store/trackerdb/utils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/store/trackerdb/version.go b/ledger/store/trackerdb/version.go index e619d2dd55..bf81dce82d 100644 --- a/ledger/store/trackerdb/version.go +++ b/ledger/store/trackerdb/version.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/accountsTotals.go b/ledger/testing/accountsTotals.go index 48109cc84c..686889c4db 100644 --- a/ledger/testing/accountsTotals.go +++ b/ledger/testing/accountsTotals.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 97693bbe2b..199a462afc 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go index f1a9c179e9..9b270e3bf7 100644 --- a/ledger/testing/consensusRange_test.go +++ b/ledger/testing/consensusRange_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/initState.go b/ledger/testing/initState.go index 374c1d6c4e..a58a7c8f21 100644 --- a/ledger/testing/initState.go +++ b/ledger/testing/initState.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/randomAccounts.go b/ledger/testing/randomAccounts.go index fd4f3b4158..2d5f79c82a 100644 --- a/ledger/testing/randomAccounts.go +++ b/ledger/testing/randomAccounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/randomAccounts_test.go b/ledger/testing/randomAccounts_test.go index 0829c4340b..9f69321aaa 100644 --- a/ledger/testing/randomAccounts_test.go +++ b/ledger/testing/randomAccounts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/testGenesis.go b/ledger/testing/testGenesis.go index 98a41d06d5..a2d469dcf0 100644 --- a/ledger/testing/testGenesis.go +++ b/ledger/testing/testGenesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/testing/withAndWithoutCache.go b/ledger/testing/withAndWithoutCache.go index 0bb1e75098..a59d296c61 100644 --- a/ledger/testing/withAndWithoutCache.go +++ b/ledger/testing/withAndWithoutCache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/tracker.go b/ledger/tracker.go index 37fa6adf5f..6d976a9763 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/tracker_test.go b/ledger/tracker_test.go index d16fb925c1..1c8a99ff41 100644 --- a/ledger/tracker_test.go +++ b/ledger/tracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/trackerdb.go b/ledger/trackerdb.go index bf23075813..581eae6f7d 100644 --- a/ledger/trackerdb.go +++ b/ledger/trackerdb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/txnbench_test.go b/ledger/txnbench_test.go index f2b4b8aa4a..9cd910f96e 100644 --- a/ledger/txnbench_test.go +++ b/ledger/txnbench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/txtail.go b/ledger/txtail.go index 6f05b47f98..e1e85fee66 100644 --- a/ledger/txtail.go +++ b/ledger/txtail.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/txtail_test.go b/ledger/txtail_test.go index ed75b591c9..fa3473fcaf 100644 --- a/ledger/txtail_test.go +++ b/ledger/txtail_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/voters.go b/ledger/voters.go index d5645ede60..63e0722a6f 100644 --- a/ledger/voters.go +++ b/ledger/voters.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/ledger/voters_test.go b/ledger/voters_test.go index 0ec6d2baad..a4913c4999 100644 --- a/ledger/voters_test.go +++ b/ledger/voters_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/accounts.go b/libgoal/accounts.go index f9e223e69a..34bbf304cd 100644 --- a/libgoal/accounts.go +++ b/libgoal/accounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/error.go b/libgoal/error.go index ca18016921..f5b9c3d76d 100644 --- a/libgoal/error.go +++ b/libgoal/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index ad98582387..75ddea8ab6 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/libgoal_test.go b/libgoal/libgoal_test.go index b2e5d22051..ec531b8e75 100644 --- a/libgoal/libgoal_test.go +++ b/libgoal/libgoal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFile.go b/libgoal/lockedFile.go index 6b09db8e3a..4678cf623e 100644 --- a/libgoal/lockedFile.go +++ b/libgoal/lockedFile.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFileLinux.go b/libgoal/lockedFileLinux.go index eb90377f10..4e26f2c5a7 100644 --- a/libgoal/lockedFileLinux.go +++ b/libgoal/lockedFileLinux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFileUnix.go b/libgoal/lockedFileUnix.go index 0d2b50e622..1113e942ab 100644 --- a/libgoal/lockedFileUnix.go +++ b/libgoal/lockedFileUnix.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/lockedFileWindows.go b/libgoal/lockedFileWindows.go index 337943a5cf..6e29fe6fa6 100644 --- a/libgoal/lockedFileWindows.go +++ b/libgoal/lockedFileWindows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/participation.go b/libgoal/participation.go index 302af5df94..73e953396b 100644 --- a/libgoal/participation.go +++ b/libgoal/participation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/participation/participation.go b/libgoal/participation/participation.go index 2d1a30e8c0..cf0b618b92 100644 --- a/libgoal/participation/participation.go +++ b/libgoal/participation/participation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/participation/participation_test.go b/libgoal/participation/participation_test.go index 69565a7800..ff6976b98e 100644 --- a/libgoal/participation/participation_test.go +++ b/libgoal/participation/participation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/system.go b/libgoal/system.go index a72214dfb0..86157044d9 100644 --- a/libgoal/system.go +++ b/libgoal/system.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/teal.go b/libgoal/teal.go index 26fcbaa638..01266437ca 100644 --- a/libgoal/teal.go +++ b/libgoal/teal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/transactions.go b/libgoal/transactions.go index 97ac2be994..f4cf38ed5b 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/unencryptedWallet.go b/libgoal/unencryptedWallet.go index 35b74d7545..c050b03a36 100644 --- a/libgoal/unencryptedWallet.go +++ b/libgoal/unencryptedWallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/walletHandles.go b/libgoal/walletHandles.go index 302d465956..5405f667d5 100644 --- a/libgoal/walletHandles.go +++ b/libgoal/walletHandles.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/libgoal/wallets.go b/libgoal/wallets.go index 3e0f12d545..e175d62f77 100644 --- a/libgoal/wallets.go +++ b/libgoal/wallets.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/collector.go b/logging/collector.go index 39203964c9..c79cba361b 100644 --- a/logging/collector.go +++ b/logging/collector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/cyclicWriter.go b/logging/cyclicWriter.go index d6166a82aa..d15782c93b 100644 --- a/logging/cyclicWriter.go +++ b/logging/cyclicWriter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/cyclicWriter_test.go b/logging/cyclicWriter_test.go index d52b826bd2..1d5de4bb1a 100644 --- a/logging/cyclicWriter_test.go +++ b/logging/cyclicWriter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logBuffer.go b/logging/logBuffer.go index 26b1045f26..9ccc958475 100644 --- a/logging/logBuffer.go +++ b/logging/logBuffer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logBuffer_test.go b/logging/logBuffer_test.go index 04a87f3f60..d3c9689e77 100644 --- a/logging/logBuffer_test.go +++ b/logging/logBuffer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/log_test.go b/logging/log_test.go index e16d62d733..ac35412cb9 100644 --- a/logging/log_test.go +++ b/logging/log_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logspec/agreement.go b/logging/logspec/agreement.go index c5df1948bd..cb5d978afc 100644 --- a/logging/logspec/agreement.go +++ b/logging/logspec/agreement.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logspec/ledger.go b/logging/logspec/ledger.go index 541917209f..d04b2087f7 100644 --- a/logging/logspec/ledger.go +++ b/logging/logspec/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/logspec/root.go b/logging/logspec/root.go index 1aa069130b..a089c650d3 100644 --- a/logging/logspec/root.go +++ b/logging/logspec/root.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetry.go b/logging/telemetry.go index 7daa471920..2ea5c02363 100644 --- a/logging/telemetry.go +++ b/logging/telemetry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryCommon.go b/logging/telemetryCommon.go index 41a13d9497..fb2163a9e0 100644 --- a/logging/telemetryCommon.go +++ b/logging/telemetryCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryConfig.go b/logging/telemetryConfig.go index 6962bb97b5..af715b382e 100644 --- a/logging/telemetryConfig.go +++ b/logging/telemetryConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryConfig_test.go b/logging/telemetryConfig_test.go index 8a61ba41c9..31faf9eced 100644 --- a/logging/telemetryConfig_test.go +++ b/logging/telemetryConfig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryFilteredHook.go b/logging/telemetryFilteredHook.go index c925577830..74e05634e7 100644 --- a/logging/telemetryFilteredHook.go +++ b/logging/telemetryFilteredHook.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetry_test.go b/logging/telemetry_test.go index 1d1b9f8d44..bf4d780e3e 100644 --- a/logging/telemetry_test.go +++ b/logging/telemetry_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryhook.go b/logging/telemetryhook.go index 077f1d14db..c166c5efce 100644 --- a/logging/telemetryhook.go +++ b/logging/telemetryhook.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryhook_test.go b/logging/telemetryhook_test.go index 43bc1acf41..31ccdc48c3 100644 --- a/logging/telemetryhook_test.go +++ b/logging/telemetryhook_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/category.go b/logging/telemetryspec/category.go index 44077c722f..80de66c671 100644 --- a/logging/telemetryspec/category.go +++ b/logging/telemetryspec/category.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/event.go b/logging/telemetryspec/event.go index 1617b0339c..3f17c1bf85 100644 --- a/logging/telemetryspec/event.go +++ b/logging/telemetryspec/event.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/eventTiming.go b/logging/telemetryspec/eventTiming.go index be6f803286..2eef89a861 100644 --- a/logging/telemetryspec/eventTiming.go +++ b/logging/telemetryspec/eventTiming.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/metric.go b/logging/telemetryspec/metric.go index e2c43cf0a6..8ab269a2c2 100644 --- a/logging/telemetryspec/metric.go +++ b/logging/telemetryspec/metric.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/telemetryspec/metric_test.go b/logging/telemetryspec/metric_test.go index 85693bdbab..d2ce4e9333 100644 --- a/logging/telemetryspec/metric_test.go +++ b/logging/telemetryspec/metric_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/testingLogger.go b/logging/testingLogger.go index 0f53e6225b..80424adb41 100644 --- a/logging/testingLogger.go +++ b/logging/testingLogger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/logging/usage.go b/logging/usage.go index 2a668ceb72..798a469819 100644 --- a/logging/usage.go +++ b/logging/usage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/network.go b/netdeploy/network.go index e4066e79ba..d5e4f522ee 100644 --- a/netdeploy/network.go +++ b/netdeploy/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index 43bb9d816f..ec3db61eb9 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/networkTemplates_test.go b/netdeploy/networkTemplates_test.go index 80f2c2a43c..69fca35441 100644 --- a/netdeploy/networkTemplates_test.go +++ b/netdeploy/networkTemplates_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/network_test.go b/netdeploy/network_test.go index cf3ea427ce..e18217b781 100644 --- a/netdeploy/network_test.go +++ b/netdeploy/network_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/bootstrappedNetwork.go b/netdeploy/remote/bootstrappedNetwork.go index bd1d3e7441..b0814a3691 100644 --- a/netdeploy/remote/bootstrappedNetwork.go +++ b/netdeploy/remote/bootstrappedNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/bootstrappedNetwork_test.go b/netdeploy/remote/bootstrappedNetwork_test.go index 45cb4162c1..c84c4f6579 100644 --- a/netdeploy/remote/bootstrappedNetwork_test.go +++ b/netdeploy/remote/bootstrappedNetwork_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/buildConfig.go b/netdeploy/remote/buildConfig.go index b7e39d6886..2fdce2efc3 100644 --- a/netdeploy/remote/buildConfig.go +++ b/netdeploy/remote/buildConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go index 16d562a554..729409fe94 100644 --- a/netdeploy/remote/deployedNetwork.go +++ b/netdeploy/remote/deployedNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/deployedNetwork_test.go b/netdeploy/remote/deployedNetwork_test.go index f6ac76b3e8..66240ee6a5 100644 --- a/netdeploy/remote/deployedNetwork_test.go +++ b/netdeploy/remote/deployedNetwork_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/hostConfig.go b/netdeploy/remote/hostConfig.go index 34af36ef41..1cbbd66c46 100644 --- a/netdeploy/remote/hostConfig.go +++ b/netdeploy/remote/hostConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/hostTemplate.go b/netdeploy/remote/hostTemplate.go index 530c80b325..b1118b7342 100644 --- a/netdeploy/remote/hostTemplate.go +++ b/netdeploy/remote/hostTemplate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index 191f434737..2c6e0e423f 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/nodeWalletData.go b/netdeploy/remote/nodeWalletData.go index 87ca8d41cc..8be7edb993 100644 --- a/netdeploy/remote/nodeWalletData.go +++ b/netdeploy/remote/nodeWalletData.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/nodecfg/nodeConfigurator.go b/netdeploy/remote/nodecfg/nodeConfigurator.go index c398e8c448..5ab43d5ff7 100644 --- a/netdeploy/remote/nodecfg/nodeConfigurator.go +++ b/netdeploy/remote/nodecfg/nodeConfigurator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/nodecfg/nodeDir.go b/netdeploy/remote/nodecfg/nodeDir.go index 8d17f01d53..9bd13343c1 100644 --- a/netdeploy/remote/nodecfg/nodeDir.go +++ b/netdeploy/remote/nodecfg/nodeDir.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/netdeploy/remote/topology.go b/netdeploy/remote/topology.go index 1f0b6d7364..eebfa3e508 100644 --- a/netdeploy/remote/topology.go +++ b/netdeploy/remote/topology.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/addr.go b/network/addr.go index f97010b4cc..1e2b04a447 100644 --- a/network/addr.go +++ b/network/addr.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/addr_test.go b/network/addr_test.go index 6763410147..377fe72a91 100644 --- a/network/addr_test.go +++ b/network/addr_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/connPerfMon.go b/network/connPerfMon.go index d254aa1279..90dacfe713 100644 --- a/network/connPerfMon.go +++ b/network/connPerfMon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/connPerfMon_test.go b/network/connPerfMon_test.go index a8398b0f36..b2f24f1214 100644 --- a/network/connPerfMon_test.go +++ b/network/connPerfMon_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/dialer.go b/network/dialer.go index 0dc3619d41..3aa59f493d 100644 --- a/network/dialer.go +++ b/network/dialer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/gossipNode.go b/network/gossipNode.go index 7ae667170a..7a916fda36 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/limited_reader_slurper.go b/network/limited_reader_slurper.go index 568b165e5e..a1ba90a5da 100644 --- a/network/limited_reader_slurper.go +++ b/network/limited_reader_slurper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/limited_reader_slurper_test.go b/network/limited_reader_slurper_test.go index bcd6eddec8..45b3fc7279 100644 --- a/network/limited_reader_slurper_test.go +++ b/network/limited_reader_slurper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messageFilter.go b/network/messageFilter.go index b42651f46e..ed19f24b5c 100644 --- a/network/messageFilter.go +++ b/network/messageFilter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messageFilter_test.go b/network/messageFilter_test.go index 1df0e2c00f..93d3dfe6f6 100644 --- a/network/messageFilter_test.go +++ b/network/messageFilter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messagetracer/graphtrace.go b/network/messagetracer/graphtrace.go index 5438c78345..e0390febe6 100644 --- a/network/messagetracer/graphtrace.go +++ b/network/messagetracer/graphtrace.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/messagetracer/interface.go b/network/messagetracer/interface.go index e684cc1797..798d954a7d 100644 --- a/network/messagetracer/interface.go +++ b/network/messagetracer/interface.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgCompressor.go b/network/msgCompressor.go index 28f835832f..831b509aef 100644 --- a/network/msgCompressor.go +++ b/network/msgCompressor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgCompressor_test.go b/network/msgCompressor_test.go index 5e3b927f90..3b08b5fc0e 100644 --- a/network/msgCompressor_test.go +++ b/network/msgCompressor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgOfInterest.go b/network/msgOfInterest.go index eb08155ba2..727405c8a8 100644 --- a/network/msgOfInterest.go +++ b/network/msgOfInterest.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/msgOfInterest_test.go b/network/msgOfInterest_test.go index c8c8dfda35..ef13c6c0cc 100644 --- a/network/msgOfInterest_test.go +++ b/network/msgOfInterest_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/multiplexer.go b/network/multiplexer.go index ddcf1845d8..0e97d63f28 100644 --- a/network/multiplexer.go +++ b/network/multiplexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/multiplexer_test.go b/network/multiplexer_test.go index c9d17dacbc..d31f8451a3 100644 --- a/network/multiplexer_test.go +++ b/network/multiplexer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/netidentity.go b/network/netidentity.go index 940ea0a633..1474739bd1 100644 --- a/network/netidentity.go +++ b/network/netidentity.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/netidentity_test.go b/network/netidentity_test.go index 9222da4600..9650069224 100644 --- a/network/netidentity_test.go +++ b/network/netidentity_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/netprio.go b/network/netprio.go index 5cb122c11f..5d91dad8d1 100644 --- a/network/netprio.go +++ b/network/netprio.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/netprio_test.go b/network/netprio_test.go index 4fc920e8c2..5de45a6074 100644 --- a/network/netprio_test.go +++ b/network/netprio_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/dnsaddr/resolve.go b/network/p2p/dnsaddr/resolve.go index 0e21a5704a..176d62f946 100644 --- a/network/p2p/dnsaddr/resolve.go +++ b/network/p2p/dnsaddr/resolve.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/dnsaddr/resolveController.go b/network/p2p/dnsaddr/resolveController.go index 7fb920ecb3..73a46243d0 100644 --- a/network/p2p/dnsaddr/resolveController.go +++ b/network/p2p/dnsaddr/resolveController.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/dnsaddr/resolveController_test.go b/network/p2p/dnsaddr/resolveController_test.go index c097240ac6..5dd3c40a9d 100644 --- a/network/p2p/dnsaddr/resolveController_test.go +++ b/network/p2p/dnsaddr/resolveController_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/dnsaddr/resolve_test.go b/network/p2p/dnsaddr/resolve_test.go index 2834a2a5e7..937e4db183 100644 --- a/network/p2p/dnsaddr/resolve_test.go +++ b/network/p2p/dnsaddr/resolve_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 94643fc61d..cd84f1c69c 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/p2p_test.go b/network/p2p/p2p_test.go index 5095e0d4b5..558131fe48 100644 --- a/network/p2p/p2p_test.go +++ b/network/p2p/p2p_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/peerID.go b/network/p2p/peerID.go index 4d808b05e9..f31d29ffb7 100644 --- a/network/p2p/peerID.go +++ b/network/p2p/peerID.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/peerID_test.go b/network/p2p/peerID_test.go index a3ce5c444d..9d7729d593 100644 --- a/network/p2p/peerID_test.go +++ b/network/p2p/peerID_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go index 63a88966ff..fa572c5912 100644 --- a/network/p2p/peerstore/peerstore.go +++ b/network/p2p/peerstore/peerstore.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go index 4263564c34..b4aa241ba4 100644 --- a/network/p2p/peerstore/peerstore_test.go +++ b/network/p2p/peerstore/peerstore_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/peerstore/utils.go b/network/p2p/peerstore/utils.go index b96fc1c8e0..02c6b2d8e6 100644 --- a/network/p2p/peerstore/utils.go +++ b/network/p2p/peerstore/utils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/peerstore/utils_test.go b/network/p2p/peerstore/utils_test.go index c8927d27ee..9f20b0f75b 100644 --- a/network/p2p/peerstore/utils_test.go +++ b/network/p2p/peerstore/utils_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/pubsub.go b/network/p2p/pubsub.go index 220320d726..372c9249c8 100644 --- a/network/p2p/pubsub.go +++ b/network/p2p/pubsub.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2p/streams.go b/network/p2p/streams.go index b6fad35d94..4a7a2d8e01 100644 --- a/network/p2p/streams.go +++ b/network/p2p/streams.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 71188b2566..c5422af849 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 3b6d127596..c67ef5b1fc 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/p2pPeer.go b/network/p2pPeer.go index 343459d243..a5065f01bb 100644 --- a/network/p2pPeer.go +++ b/network/p2pPeer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/peersheap.go b/network/peersheap.go index 03224b0ea5..35a6fa4781 100644 --- a/network/peersheap.go +++ b/network/peersheap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/phonebook.go b/network/phonebook.go index cf189eb6a4..3f196e0605 100644 --- a/network/phonebook.go +++ b/network/phonebook.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/phonebook_test.go b/network/phonebook_test.go index 4fdad53c42..36365c5916 100644 --- a/network/phonebook_test.go +++ b/network/phonebook_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/rateLimitingTransport.go b/network/rateLimitingTransport.go index a3fd332cb5..461a468da5 100644 --- a/network/rateLimitingTransport.go +++ b/network/rateLimitingTransport.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestLogger.go b/network/requestLogger.go index 562abd9f56..80488cbe91 100644 --- a/network/requestLogger.go +++ b/network/requestLogger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestLogger_test.go b/network/requestLogger_test.go index a17d054e43..cb1d7b963d 100644 --- a/network/requestLogger_test.go +++ b/network/requestLogger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestTracker.go b/network/requestTracker.go index 63fd4a72b6..c88d4e5cf0 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index 65349987ea..0a8c934c53 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/topics.go b/network/topics.go index 5cb0370734..679075c839 100644 --- a/network/topics.go +++ b/network/topics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/topics_test.go b/network/topics_test.go index b01ede7ff3..65f96b6a11 100644 --- a/network/topics_test.go +++ b/network/topics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/websocketProxy_test.go b/network/websocketProxy_test.go index e0888c5449..73298ccd64 100644 --- a/network/websocketProxy_test.go +++ b/network/websocketProxy_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/wsNetwork.go b/network/wsNetwork.go index d316fcd814..9ce5f331ae 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 7bc3b64f1e..b7cd873bcf 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/wsPeer.go b/network/wsPeer.go index 7cbdbeaebc..adf211f592 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go index 91cd3cb37d..d1f32302a0 100644 --- a/network/wsPeer_test.go +++ b/network/wsPeer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/assemble_test.go b/node/assemble_test.go index 8b0a8afaeb..6954d99400 100644 --- a/node/assemble_test.go +++ b/node/assemble_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/error.go b/node/error.go index f3c3e05ea0..ea4ad83575 100644 --- a/node/error.go +++ b/node/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/follower_node.go b/node/follower_node.go index c61c379577..277c48a24e 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/follower_node_test.go b/node/follower_node_test.go index b6402a9b57..cd82ea4e34 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/impls.go b/node/impls.go index a33bc6e737..b7ee54fab7 100644 --- a/node/impls.go +++ b/node/impls.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/netprio.go b/node/netprio.go index 79a669056a..f3ac6a488f 100644 --- a/node/netprio.go +++ b/node/netprio.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/netprio_test.go b/node/netprio_test.go index 489099e5c9..bf34fff102 100644 --- a/node/netprio_test.go +++ b/node/netprio_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/node.go b/node/node.go index e1f79907c8..75d9bcfa27 100644 --- a/node/node.go +++ b/node/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/node/node_test.go b/node/node_test.go index 55cbae9368..dabb7958a5 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/LaggedStdIo.go b/nodecontrol/LaggedStdIo.go index 1638dd118e..c46efb92da 100644 --- a/nodecontrol/LaggedStdIo.go +++ b/nodecontrol/LaggedStdIo.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/NodeController.go b/nodecontrol/NodeController.go index e9ce178b8e..3535c2127c 100644 --- a/nodecontrol/NodeController.go +++ b/nodecontrol/NodeController.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/algodControl.go b/nodecontrol/algodControl.go index 3702e21167..74137a72eb 100644 --- a/nodecontrol/algodControl.go +++ b/nodecontrol/algodControl.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/algodControl_test.go b/nodecontrol/algodControl_test.go index d81d24d9e6..af6e48bfbe 100644 --- a/nodecontrol/algodControl_test.go +++ b/nodecontrol/algodControl_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/kmdControl.go b/nodecontrol/kmdControl.go index 3c8936783a..4dfb3eb747 100644 --- a/nodecontrol/kmdControl.go +++ b/nodecontrol/kmdControl.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/kmdControl_common.go b/nodecontrol/kmdControl_common.go index 9e4d96feaf..c26a5085d1 100644 --- a/nodecontrol/kmdControl_common.go +++ b/nodecontrol/kmdControl_common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/kmdControl_windows.go b/nodecontrol/kmdControl_windows.go index 2273295e06..39e37a44e8 100644 --- a/nodecontrol/kmdControl_windows.go +++ b/nodecontrol/kmdControl_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/nodecontrol/nodeControlErrors.go b/nodecontrol/nodeControlErrors.go index 8502b252d2..ba214534bd 100644 --- a/nodecontrol/nodeControlErrors.go +++ b/nodecontrol/nodeControlErrors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/codec.go b/protocol/codec.go index 62abaedad6..97dbccbc95 100644 --- a/protocol/codec.go +++ b/protocol/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/codec_test.go b/protocol/codec_test.go index d11a47a909..72ac537b22 100644 --- a/protocol/codec_test.go +++ b/protocol/codec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/codec_tester.go b/protocol/codec_tester.go index 49cae937bf..63a6aaf0a1 100644 --- a/protocol/codec_tester.go +++ b/protocol/codec_tester.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/consensus.go b/protocol/consensus.go index 928ba4b6f0..51c023ec2a 100644 --- a/protocol/consensus.go +++ b/protocol/consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/encodebench_test.go b/protocol/encodebench_test.go index 32267a5d43..b0f26c1614 100644 --- a/protocol/encodebench_test.go +++ b/protocol/encodebench_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/hash.go b/protocol/hash.go index 434deaf8c3..906afb2c3d 100644 --- a/protocol/hash.go +++ b/protocol/hash.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/hash_test.go b/protocol/hash_test.go index b3885d18ce..12eca20cb4 100644 --- a/protocol/hash_test.go +++ b/protocol/hash_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/networks.go b/protocol/networks.go index a6e3b13d1f..b43f939d80 100644 --- a/protocol/networks.go +++ b/protocol/networks.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/stateproof.go b/protocol/stateproof.go index 1e55ef652c..7bb1a60fbb 100644 --- a/protocol/stateproof.go +++ b/protocol/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/tags.go b/protocol/tags.go index a9eb2a6bbc..e980454674 100644 --- a/protocol/tags.go +++ b/protocol/tags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/tags_test.go b/protocol/tags_test.go index 7cf0bea93b..137bf4e3f7 100644 --- a/protocol/tags_test.go +++ b/protocol/tags_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/test/allocbound_slice.go b/protocol/test/allocbound_slice.go index 1d792d5688..b9f696e3c9 100644 --- a/protocol/test/allocbound_slice.go +++ b/protocol/test/allocbound_slice.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/transcode/core.go b/protocol/transcode/core.go index eaf6d00e00..db97e3daa2 100644 --- a/protocol/transcode/core.go +++ b/protocol/transcode/core.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/transcode/core_test.go b/protocol/transcode/core_test.go index 34afe8f54d..55c05d4612 100644 --- a/protocol/transcode/core_test.go +++ b/protocol/transcode/core_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/protocol/txntype.go b/protocol/txntype.go index 0728898f1a..76cb2dc406 100644 --- a/protocol/txntype.go +++ b/protocol/txntype.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 2d4a4b822e..7245eb1881 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index 832b59c55b..e77fc7aa0e 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/httpTxSync.go b/rpcs/httpTxSync.go index 6f42eeaacf..43258337cb 100644 --- a/rpcs/httpTxSync.go +++ b/rpcs/httpTxSync.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go index a3ff63e90e..b3742bb985 100644 --- a/rpcs/ledgerService.go +++ b/rpcs/ledgerService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/ledgerService_test.go b/rpcs/ledgerService_test.go index 1285795d4c..1cc52fc9c0 100644 --- a/rpcs/ledgerService_test.go +++ b/rpcs/ledgerService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/registrar.go b/rpcs/registrar.go index f119193298..f488aebf26 100644 --- a/rpcs/registrar.go +++ b/rpcs/registrar.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txService.go b/rpcs/txService.go index 3083e2d33d..e9eb5d7aa2 100644 --- a/rpcs/txService.go +++ b/rpcs/txService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txService_test.go b/rpcs/txService_test.go index 8ef49e45a6..1e7a5036e4 100644 --- a/rpcs/txService_test.go +++ b/rpcs/txService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txSyncer.go b/rpcs/txSyncer.go index c724417ea3..1d7fc2c027 100644 --- a/rpcs/txSyncer.go +++ b/rpcs/txSyncer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/rpcs/txSyncer_test.go b/rpcs/txSyncer_test.go index 43e85f4523..1c3f071879 100644 --- a/rpcs/txSyncer_test.go +++ b/rpcs/txSyncer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/shared/algoh/config.go b/shared/algoh/config.go index a4cec444d1..12e815dfa1 100644 --- a/shared/algoh/config.go +++ b/shared/algoh/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/shared/pingpong/accounts.go b/shared/pingpong/accounts.go index 3c34469f3b..99eb4b25bc 100644 --- a/shared/pingpong/accounts.go +++ b/shared/pingpong/accounts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/shared/pingpong/accounts_test.go b/shared/pingpong/accounts_test.go index 08def89944..2b2fa4cad2 100644 --- a/shared/pingpong/accounts_test.go +++ b/shared/pingpong/accounts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/shared/pingpong/config.go b/shared/pingpong/config.go index 6d3057e9bb..5b4042dee9 100644 --- a/shared/pingpong/config.go +++ b/shared/pingpong/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go index 95aeac0391..412d59c54f 100644 --- a/shared/pingpong/pingpong.go +++ b/shared/pingpong/pingpong.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/abstractions.go b/stateproof/abstractions.go index 67f4731882..5832fed87a 100644 --- a/stateproof/abstractions.go +++ b/stateproof/abstractions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/builder.go b/stateproof/builder.go index 13f4aa5b9b..317e813602 100644 --- a/stateproof/builder.go +++ b/stateproof/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/db.go b/stateproof/db.go index 46618a1f74..e89942280d 100644 --- a/stateproof/db.go +++ b/stateproof/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/db_test.go b/stateproof/db_test.go index 5fc1365548..d497b92405 100644 --- a/stateproof/db_test.go +++ b/stateproof/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/recovery.go b/stateproof/recovery.go index 6bd20337f1..3585e3ab1a 100644 --- a/stateproof/recovery.go +++ b/stateproof/recovery.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/signer.go b/stateproof/signer.go index 99c715fb91..117839b352 100644 --- a/stateproof/signer.go +++ b/stateproof/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/stateproofMessageGenerator.go b/stateproof/stateproofMessageGenerator.go index c51d767195..cadca13c2d 100644 --- a/stateproof/stateproofMessageGenerator.go +++ b/stateproof/stateproofMessageGenerator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/stateproofMessageGenerator_test.go b/stateproof/stateproofMessageGenerator_test.go index a990143b05..a9db6da2d5 100644 --- a/stateproof/stateproofMessageGenerator_test.go +++ b/stateproof/stateproofMessageGenerator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/verify/stateproof.go b/stateproof/verify/stateproof.go index 3caaabac86..50ea86a840 100644 --- a/stateproof/verify/stateproof.go +++ b/stateproof/verify/stateproof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/verify/stateproof_test.go b/stateproof/verify/stateproof_test.go index b092bad8fe..b82ef0b2bb 100644 --- a/stateproof/verify/stateproof_test.go +++ b/stateproof/verify/stateproof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/worker.go b/stateproof/worker.go index e9aa84f85d..e73a06d137 100644 --- a/stateproof/worker.go +++ b/stateproof/worker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/stateproof/worker_test.go b/stateproof/worker_test.go index 68a19dcd21..8521cac93d 100644 --- a/stateproof/worker_test.go +++ b/stateproof/worker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/component/agent.go b/test/commandandcontrol/cc_agent/component/agent.go index 4f01065b65..1a4e738b0d 100644 --- a/test/commandandcontrol/cc_agent/component/agent.go +++ b/test/commandandcontrol/cc_agent/component/agent.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/component/agent_test.go b/test/commandandcontrol/cc_agent/component/agent_test.go index b819ab83c9..b827241e5c 100644 --- a/test/commandandcontrol/cc_agent/component/agent_test.go +++ b/test/commandandcontrol/cc_agent/component/agent_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/component/pingPongComponent.go b/test/commandandcontrol/cc_agent/component/pingPongComponent.go index ffc61fa5de..70e3980b61 100644 --- a/test/commandandcontrol/cc_agent/component/pingPongComponent.go +++ b/test/commandandcontrol/cc_agent/component/pingPongComponent.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_agent/main.go b/test/commandandcontrol/cc_agent/main.go index 5adcfa5622..db8e529434 100644 --- a/test/commandandcontrol/cc_agent/main.go +++ b/test/commandandcontrol/cc_agent/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_client/main.go b/test/commandandcontrol/cc_client/main.go index 6b64117b0b..674b795210 100644 --- a/test/commandandcontrol/cc_client/main.go +++ b/test/commandandcontrol/cc_client/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/cc_service/main.go b/test/commandandcontrol/cc_service/main.go index fa39f9229c..b0d58f721d 100644 --- a/test/commandandcontrol/cc_service/main.go +++ b/test/commandandcontrol/cc_service/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/commandandcontrol/lib/ccCommon.go b/test/commandandcontrol/lib/ccCommon.go index 36cff09ec5..a733aefc53 100644 --- a/test/commandandcontrol/lib/ccCommon.go +++ b/test/commandandcontrol/lib/ccCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algod/cleanup_test.go b/test/e2e-go/cli/algod/cleanup_test.go index 9e690b45a3..20ec8aeb77 100644 --- a/test/e2e-go/cli/algod/cleanup_test.go +++ b/test/e2e-go/cli/algod/cleanup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algod/expect/algod_expect_test.go b/test/e2e-go/cli/algod/expect/algod_expect_test.go index 25850d059f..482d6ddecd 100644 --- a/test/e2e-go/cli/algod/expect/algod_expect_test.go +++ b/test/e2e-go/cli/algod/expect/algod_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algod/stdstreams_test.go b/test/e2e-go/cli/algod/stdstreams_test.go index f2a412752a..3d1e7e189a 100644 --- a/test/e2e-go/cli/algod/stdstreams_test.go +++ b/test/e2e-go/cli/algod/stdstreams_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/algoh/expect/algoh_expect_test.go b/test/e2e-go/cli/algoh/expect/algoh_expect_test.go index 8a94200a6b..873708b66f 100644 --- a/test/e2e-go/cli/algoh/expect/algoh_expect_test.go +++ b/test/e2e-go/cli/algoh/expect/algoh_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/account_test.go b/test/e2e-go/cli/goal/account_test.go index fe05ccddb3..f26cf7e11e 100644 --- a/test/e2e-go/cli/goal/account_test.go +++ b/test/e2e-go/cli/goal/account_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/clerk_test.go b/test/e2e-go/cli/goal/clerk_test.go index 165c65e9fa..ff02474210 100644 --- a/test/e2e-go/cli/goal/clerk_test.go +++ b/test/e2e-go/cli/goal/clerk_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/common_test.go b/test/e2e-go/cli/goal/common_test.go index a233d87370..a6d86944be 100644 --- a/test/e2e-go/cli/goal/common_test.go +++ b/test/e2e-go/cli/goal/common_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go b/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go index 6b4ac0f311..6b3a2282fb 100644 --- a/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go +++ b/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/expect/goal_expect_test.go b/test/e2e-go/cli/goal/expect/goal_expect_test.go index b90dd17d4b..f6d1be4624 100644 --- a/test/e2e-go/cli/goal/expect/goal_expect_test.go +++ b/test/e2e-go/cli/goal/expect/goal_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/goal/node_cleanup_test.go b/test/e2e-go/cli/goal/node_cleanup_test.go index ae70c89603..a472d575c4 100644 --- a/test/e2e-go/cli/goal/node_cleanup_test.go +++ b/test/e2e-go/cli/goal/node_cleanup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/perf/libgoal_test.go b/test/e2e-go/cli/perf/libgoal_test.go index a3e7581c67..6843189e09 100644 --- a/test/e2e-go/cli/perf/libgoal_test.go +++ b/test/e2e-go/cli/perf/libgoal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/perf/payment_test.go b/test/e2e-go/cli/perf/payment_test.go index fc60e16b84..8c875e0d54 100644 --- a/test/e2e-go/cli/perf/payment_test.go +++ b/test/e2e-go/cli/perf/payment_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/tealdbg/cdtmock/main.go b/test/e2e-go/cli/tealdbg/cdtmock/main.go index 6d1292e4b5..4aee6e4b1a 100644 --- a/test/e2e-go/cli/tealdbg/cdtmock/main.go +++ b/test/e2e-go/cli/tealdbg/cdtmock/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go b/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go index aedf8a2d9a..9ebfdec6fd 100644 --- a/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go +++ b/test/e2e-go/cli/tealdbg/expect/tealdbg_expect_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/accountPerf/sixMillion_test.go b/test/e2e-go/features/accountPerf/sixMillion_test.go index 7548b259bd..946d1b24b6 100644 --- a/test/e2e-go/features/accountPerf/sixMillion_test.go +++ b/test/e2e-go/features/accountPerf/sixMillion_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/catchup/basicCatchup_test.go b/test/e2e-go/features/catchup/basicCatchup_test.go index f6a009dc19..2e3ac87943 100644 --- a/test/e2e-go/features/catchup/basicCatchup_test.go +++ b/test/e2e-go/features/catchup/basicCatchup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index eb83451307..6bc07e6cac 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/catchup/stateproofsCatchup_test.go b/test/e2e-go/features/catchup/stateproofsCatchup_test.go index 4d3140c8d0..9de6bf385c 100644 --- a/test/e2e-go/features/catchup/stateproofsCatchup_test.go +++ b/test/e2e-go/features/catchup/stateproofsCatchup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/devmode/devmode_test.go b/test/e2e-go/features/devmode/devmode_test.go index 5c2545a33b..c2303085c1 100644 --- a/test/e2e-go/features/devmode/devmode_test.go +++ b/test/e2e-go/features/devmode/devmode_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/followernode/syncDeltas_test.go b/test/e2e-go/features/followernode/syncDeltas_test.go index bd3d8e0d88..b404a2a5ef 100644 --- a/test/e2e-go/features/followernode/syncDeltas_test.go +++ b/test/e2e-go/features/followernode/syncDeltas_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/followernode/syncRestart_test.go b/test/e2e-go/features/followernode/syncRestart_test.go index 9bdd686f44..589bb7b53c 100644 --- a/test/e2e-go/features/followernode/syncRestart_test.go +++ b/test/e2e-go/features/followernode/syncRestart_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/multisig/multisig_test.go b/test/e2e-go/features/multisig/multisig_test.go index 3452b1b505..6d7b1c616d 100644 --- a/test/e2e-go/features/multisig/multisig_test.go +++ b/test/e2e-go/features/multisig/multisig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/p2p/p2p_basic_test.go b/test/e2e-go/features/p2p/p2p_basic_test.go index 726a77cfe5..6f3e8aae47 100644 --- a/test/e2e-go/features/p2p/p2p_basic_test.go +++ b/test/e2e-go/features/p2p/p2p_basic_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/accountParticipationTransitions_test.go b/test/e2e-go/features/participation/accountParticipationTransitions_test.go index 6752af841b..1e5339f13c 100644 --- a/test/e2e-go/features/participation/accountParticipationTransitions_test.go +++ b/test/e2e-go/features/participation/accountParticipationTransitions_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/deletePartKeys_test.go b/test/e2e-go/features/participation/deletePartKeys_test.go index c72925dba7..aba68a0ce4 100644 --- a/test/e2e-go/features/participation/deletePartKeys_test.go +++ b/test/e2e-go/features/participation/deletePartKeys_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go index 18c985c205..38f2e4bc35 100644 --- a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go +++ b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/overlappingParticipationKeys_test.go b/test/e2e-go/features/participation/overlappingParticipationKeys_test.go index b6df5b4413..60f3471b7a 100644 --- a/test/e2e-go/features/participation/overlappingParticipationKeys_test.go +++ b/test/e2e-go/features/participation/overlappingParticipationKeys_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/participationExpiration_test.go b/test/e2e-go/features/participation/participationExpiration_test.go index 3bb915ab0c..5cb4f941dc 100644 --- a/test/e2e-go/features/participation/participationExpiration_test.go +++ b/test/e2e-go/features/participation/participationExpiration_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/participation/participationRewards_test.go b/test/e2e-go/features/participation/participationRewards_test.go index a9c81f6a48..8c0fb64a8d 100644 --- a/test/e2e-go/features/participation/participationRewards_test.go +++ b/test/e2e-go/features/participation/participationRewards_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go b/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go index 3f16be7f68..284146864d 100644 --- a/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go +++ b/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/privatenet/privatenet_test.go b/test/e2e-go/features/privatenet/privatenet_test.go index 312abed618..34c4119cc1 100644 --- a/test/e2e-go/features/privatenet/privatenet_test.go +++ b/test/e2e-go/features/privatenet/privatenet_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index 287553acf9..85d7d5e127 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/teal/compile_test.go b/test/e2e-go/features/teal/compile_test.go index da15381120..31a3c078e5 100644 --- a/test/e2e-go/features/teal/compile_test.go +++ b/test/e2e-go/features/teal/compile_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/accountv2_test.go b/test/e2e-go/features/transactions/accountv2_test.go index fc6a828889..fb255fa9c2 100644 --- a/test/e2e-go/features/transactions/accountv2_test.go +++ b/test/e2e-go/features/transactions/accountv2_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/app_pages_test.go b/test/e2e-go/features/transactions/app_pages_test.go index f9ce0878a0..710c00fc7f 100644 --- a/test/e2e-go/features/transactions/app_pages_test.go +++ b/test/e2e-go/features/transactions/app_pages_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/application_test.go b/test/e2e-go/features/transactions/application_test.go index a93794bb27..bbe5abf2d4 100644 --- a/test/e2e-go/features/transactions/application_test.go +++ b/test/e2e-go/features/transactions/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 2b38e7d515..82e949d0bb 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/close_account_test.go b/test/e2e-go/features/transactions/close_account_test.go index 3426ea957b..a0f42a40d5 100644 --- a/test/e2e-go/features/transactions/close_account_test.go +++ b/test/e2e-go/features/transactions/close_account_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/group_test.go b/test/e2e-go/features/transactions/group_test.go index 1d8341f42c..117689f266 100644 --- a/test/e2e-go/features/transactions/group_test.go +++ b/test/e2e-go/features/transactions/group_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/lease_test.go b/test/e2e-go/features/transactions/lease_test.go index 093ec2273b..47328369eb 100644 --- a/test/e2e-go/features/transactions/lease_test.go +++ b/test/e2e-go/features/transactions/lease_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/onlineStatusChange_test.go b/test/e2e-go/features/transactions/onlineStatusChange_test.go index 7a9e6b3467..04a5144f25 100644 --- a/test/e2e-go/features/transactions/onlineStatusChange_test.go +++ b/test/e2e-go/features/transactions/onlineStatusChange_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/proof_test.go b/test/e2e-go/features/transactions/proof_test.go index f7d8141d7b..6588174f36 100644 --- a/test/e2e-go/features/transactions/proof_test.go +++ b/test/e2e-go/features/transactions/proof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/features/transactions/sendReceive_test.go b/test/e2e-go/features/transactions/sendReceive_test.go index 1a261a8442..9d909beedc 100644 --- a/test/e2e-go/features/transactions/sendReceive_test.go +++ b/test/e2e-go/features/transactions/sendReceive_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/globals/constants.go b/test/e2e-go/globals/constants.go index b289379a10..fd43a105dc 100644 --- a/test/e2e-go/globals/constants.go +++ b/test/e2e-go/globals/constants.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_server_client_test.go b/test/e2e-go/kmd/e2e_kmd_server_client_test.go index 7d7977a0b0..601b3b5acd 100644 --- a/test/e2e-go/kmd/e2e_kmd_server_client_test.go +++ b/test/e2e-go/kmd/e2e_kmd_server_client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_sqlite_test.go b/test/e2e-go/kmd/e2e_kmd_sqlite_test.go index d8290ae5e3..441ab20323 100644 --- a/test/e2e-go/kmd/e2e_kmd_sqlite_test.go +++ b/test/e2e-go/kmd/e2e_kmd_sqlite_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go b/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go index c5dd733e0d..3555b49ceb 100644 --- a/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go +++ b/test/e2e-go/kmd/e2e_kmd_wallet_keyops_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go b/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go index 8995b70c7c..d4367bbdc3 100644 --- a/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go +++ b/test/e2e-go/kmd/e2e_kmd_wallet_multisig_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/kmd/e2e_kmd_wallet_test.go b/test/e2e-go/kmd/e2e_kmd_wallet_test.go index 558c4ff30d..f3e5b87a2e 100644 --- a/test/e2e-go/kmd/e2e_kmd_wallet_test.go +++ b/test/e2e-go/kmd/e2e_kmd_wallet_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/perf/basic_test.go b/test/e2e-go/perf/basic_test.go index be8a4c8197..8509c40006 100644 --- a/test/e2e-go/perf/basic_test.go +++ b/test/e2e-go/perf/basic_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/restAPI/helpers.go b/test/e2e-go/restAPI/helpers.go index 3e85020e94..688ac373ea 100644 --- a/test/e2e-go/restAPI/helpers.go +++ b/test/e2e-go/restAPI/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/restAPI/other/appsRestAPI_test.go b/test/e2e-go/restAPI/other/appsRestAPI_test.go index 8abe6a4dbc..51d757af15 100644 --- a/test/e2e-go/restAPI/other/appsRestAPI_test.go +++ b/test/e2e-go/restAPI/other/appsRestAPI_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/restAPI/other/misc_test.go b/test/e2e-go/restAPI/other/misc_test.go index b77a844527..3f9da07c4a 100644 --- a/test/e2e-go/restAPI/other/misc_test.go +++ b/test/e2e-go/restAPI/other/misc_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 65292563da..8f3298170a 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go index 9f4b66d57b..a30ce06b4f 100644 --- a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go +++ b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go b/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go index 497d128643..f539bf3a33 100644 --- a/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go +++ b/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go b/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go index a7e5c97555..57f2735746 100644 --- a/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go +++ b/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/application_support_test.go b/test/e2e-go/upgrades/application_support_test.go index 8746545d08..49635a43ac 100644 --- a/test/e2e-go/upgrades/application_support_test.go +++ b/test/e2e-go/upgrades/application_support_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/rekey_support_test.go b/test/e2e-go/upgrades/rekey_support_test.go index 4c7878bfaa..30226c795f 100644 --- a/test/e2e-go/upgrades/rekey_support_test.go +++ b/test/e2e-go/upgrades/rekey_support_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/send_receive_upgrade_test.go b/test/e2e-go/upgrades/send_receive_upgrade_test.go index b6b4134236..f9e0746cbc 100644 --- a/test/e2e-go/upgrades/send_receive_upgrade_test.go +++ b/test/e2e-go/upgrades/send_receive_upgrade_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/e2e-go/upgrades/stateproof_participation_test.go b/test/e2e-go/upgrades/stateproof_participation_test.go index 8823d7c769..5c6a6ab940 100644 --- a/test/e2e-go/upgrades/stateproof_participation_test.go +++ b/test/e2e-go/upgrades/stateproof_participation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/baseFixture.go b/test/framework/fixtures/baseFixture.go index 918fa6d7d5..4701508e8d 100644 --- a/test/framework/fixtures/baseFixture.go +++ b/test/framework/fixtures/baseFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/expectFixture.go b/test/framework/fixtures/expectFixture.go index 50206bda12..92d2daed80 100644 --- a/test/framework/fixtures/expectFixture.go +++ b/test/framework/fixtures/expectFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/fixture.go b/test/framework/fixtures/fixture.go index 4ccba1accf..a307534d3f 100644 --- a/test/framework/fixtures/fixture.go +++ b/test/framework/fixtures/fixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/goalFixture.go b/test/framework/fixtures/goalFixture.go index 69e43e784a..15e2fafa7e 100644 --- a/test/framework/fixtures/goalFixture.go +++ b/test/framework/fixtures/goalFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/kmdFixture.go b/test/framework/fixtures/kmdFixture.go index 21c70dd316..7e747bb914 100644 --- a/test/framework/fixtures/kmdFixture.go +++ b/test/framework/fixtures/kmdFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 58ca1662b3..474c902186 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index e0151630a4..40e12c408b 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/framework/fixtures/webProxyFixture.go b/test/framework/fixtures/webProxyFixture.go index c048875809..87bf95f0f0 100644 --- a/test/framework/fixtures/webProxyFixture.go +++ b/test/framework/fixtures/webProxyFixture.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/block_history.py b/test/heapwatch/block_history.py index c7fa3ce6f5..665cd6c743 100644 --- a/test/heapwatch/block_history.py +++ b/test/heapwatch/block_history.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/block_history_plot.py b/test/heapwatch/block_history_plot.py index 03f5a45221..7de45e21b0 100644 --- a/test/heapwatch/block_history_plot.py +++ b/test/heapwatch/block_history_plot.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/block_history_relays.py b/test/heapwatch/block_history_relays.py index ebf310587c..fd60a3dfe8 100644 --- a/test/heapwatch/block_history_relays.py +++ b/test/heapwatch/block_history_relays.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/client_ram_report.py b/test/heapwatch/client_ram_report.py index 7833ababa3..97a1171630 100644 --- a/test/heapwatch/client_ram_report.py +++ b/test/heapwatch/client_ram_report.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/heapWatch.py b/test/heapwatch/heapWatch.py index 68af9cd3b8..e62cc7ab80 100644 --- a/test/heapwatch/heapWatch.py +++ b/test/heapwatch/heapWatch.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 # -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/metrics_delta.py b/test/heapwatch/metrics_delta.py index 420130df91..50b1e9e2e3 100644 --- a/test/heapwatch/metrics_delta.py +++ b/test/heapwatch/metrics_delta.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/nodeHostTarget.py b/test/heapwatch/nodeHostTarget.py index ccab46ba1c..1b617c9d25 100644 --- a/test/heapwatch/nodeHostTarget.py +++ b/test/heapwatch/nodeHostTarget.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/heapwatch/runNodeHost.py b/test/heapwatch/runNodeHost.py index be2ce61bea..5625ec61bb 100644 --- a/test/heapwatch/runNodeHost.py +++ b/test/heapwatch/runNodeHost.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -# Copyright (C) 2019-2023 Algorand, Inc. +# Copyright (C) 2019-2024 Algorand, Inc. # This file is part of go-algorand # # go-algorand is free software: you can redistribute it and/or modify diff --git a/test/linttest/lintissues.go b/test/linttest/lintissues.go index cdfdbf422b..5f21543d99 100644 --- a/test/linttest/lintissues.go +++ b/test/linttest/lintissues.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/main.go b/test/netperf-go/puppeteer/main.go index fbebeb705c..691dd663b8 100644 --- a/test/netperf-go/puppeteer/main.go +++ b/test/netperf-go/puppeteer/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/promMetricFetcher.go b/test/netperf-go/puppeteer/promMetricFetcher.go index ea24008fa4..cd268d0db1 100644 --- a/test/netperf-go/puppeteer/promMetricFetcher.go +++ b/test/netperf-go/puppeteer/promMetricFetcher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/promMetricFetcher_test.go b/test/netperf-go/puppeteer/promMetricFetcher_test.go index 200a9af258..ee95922b27 100644 --- a/test/netperf-go/puppeteer/promMetricFetcher_test.go +++ b/test/netperf-go/puppeteer/promMetricFetcher_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/puppeteer.go b/test/netperf-go/puppeteer/puppeteer.go index 260c659677..c317742910 100644 --- a/test/netperf-go/puppeteer/puppeteer.go +++ b/test/netperf-go/puppeteer/puppeteer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/puppeteer_test.go b/test/netperf-go/puppeteer/puppeteer_test.go index e202751439..b86b1cd1a6 100644 --- a/test/netperf-go/puppeteer/puppeteer_test.go +++ b/test/netperf-go/puppeteer/puppeteer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/netperf-go/puppeteer/roundpoller.go b/test/netperf-go/puppeteer/roundpoller.go index 2ce501a2a4..63156ff89c 100644 --- a/test/netperf-go/puppeteer/roundpoller.go +++ b/test/netperf-go/puppeteer/roundpoller.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/partitiontest/filtering.go b/test/partitiontest/filtering.go index 19820f6014..de738ed71d 100644 --- a/test/partitiontest/filtering.go +++ b/test/partitiontest/filtering.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/test/reflectionhelpers/helpers.go b/test/reflectionhelpers/helpers.go index 6d228b061f..de11d3c9e9 100644 --- a/test/reflectionhelpers/helpers.go +++ b/test/reflectionhelpers/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/core/commands.go b/tools/block-generator/core/commands.go index af64c8a5d7..cf4396324a 100644 --- a/tools/block-generator/core/commands.go +++ b/tools/block-generator/core/commands.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/config.go b/tools/block-generator/generator/config.go index 912696f11c..55097b9ee1 100644 --- a/tools/block-generator/generator/config.go +++ b/tools/block-generator/generator/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/config_test.go b/tools/block-generator/generator/config_test.go index a595ad6d69..634ea3497e 100644 --- a/tools/block-generator/generator/config_test.go +++ b/tools/block-generator/generator/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/daemon.go b/tools/block-generator/generator/daemon.go index 0a1371a1bc..8284fa559d 100644 --- a/tools/block-generator/generator/daemon.go +++ b/tools/block-generator/generator/daemon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 44c5444876..45cf6775c8 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/generate_apps.go b/tools/block-generator/generator/generate_apps.go index 8edd28da57..972cf1e8ac 100644 --- a/tools/block-generator/generator/generate_apps.go +++ b/tools/block-generator/generator/generate_apps.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go index 1ce40b0f13..2c210ec4e4 100644 --- a/tools/block-generator/generator/generate_test.go +++ b/tools/block-generator/generator/generate_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/generator_ledger.go b/tools/block-generator/generator/generator_ledger.go index dabad6ac21..f841b574f3 100644 --- a/tools/block-generator/generator/generator_ledger.go +++ b/tools/block-generator/generator/generator_ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/generator_types.go b/tools/block-generator/generator/generator_types.go index 622ddaf96d..6cebf76861 100644 --- a/tools/block-generator/generator/generator_types.go +++ b/tools/block-generator/generator/generator_types.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/make_transactions.go b/tools/block-generator/generator/make_transactions.go index 6caffe25b7..558dfd956f 100644 --- a/tools/block-generator/generator/make_transactions.go +++ b/tools/block-generator/generator/make_transactions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go index 5b170c504e..8bd4c5139f 100644 --- a/tools/block-generator/generator/server.go +++ b/tools/block-generator/generator/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/server_test.go b/tools/block-generator/generator/server_test.go index a885181ddd..d4b1c8f551 100644 --- a/tools/block-generator/generator/server_test.go +++ b/tools/block-generator/generator/server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/utils.go b/tools/block-generator/generator/utils.go index 5f048a0253..17cd5a8658 100644 --- a/tools/block-generator/generator/utils.go +++ b/tools/block-generator/generator/utils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/generator/utils_test.go b/tools/block-generator/generator/utils_test.go index fb69a43fee..295672669e 100644 --- a/tools/block-generator/generator/utils_test.go +++ b/tools/block-generator/generator/utils_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/main.go b/tools/block-generator/main.go index 6525bc4a6c..6b5bee80a1 100644 --- a/tools/block-generator/main.go +++ b/tools/block-generator/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/runner/metrics_collector.go b/tools/block-generator/runner/metrics_collector.go index 394bf32cba..f74a41f119 100644 --- a/tools/block-generator/runner/metrics_collector.go +++ b/tools/block-generator/runner/metrics_collector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/runner/reporting_test.go b/tools/block-generator/runner/reporting_test.go index 56d73b17a7..cebf6ea549 100644 --- a/tools/block-generator/runner/reporting_test.go +++ b/tools/block-generator/runner/reporting_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index f2e1c1fc1f..af5c9fe7b1 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go index 4c11fde59c..c077415c75 100644 --- a/tools/block-generator/runner/runner.go +++ b/tools/block-generator/runner/runner.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/block-generator/util/util.go b/tools/block-generator/util/util.go index ccaafab84f..0c4e3bdf47 100644 --- a/tools/block-generator/util/util.go +++ b/tools/block-generator/util/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/boxkey/convertBoxKey.go b/tools/boxkey/convertBoxKey.go index 0d77d2f84b..5b87007a54 100644 --- a/tools/boxkey/convertBoxKey.go +++ b/tools/boxkey/convertBoxKey.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/algodump/main.go b/tools/debug/algodump/main.go index 429cec9f9d..4899fb0c73 100644 --- a/tools/debug/algodump/main.go +++ b/tools/debug/algodump/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/carpenter/main.go b/tools/debug/carpenter/main.go index 57e90a810d..96ca5296a0 100644 --- a/tools/debug/carpenter/main.go +++ b/tools/debug/carpenter/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/chopper/main.go b/tools/debug/chopper/main.go index 5e70292f07..a09172c083 100644 --- a/tools/debug/chopper/main.go +++ b/tools/debug/chopper/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/coroner/main.go b/tools/debug/coroner/main.go index 1ea04537d4..c8670b7b14 100644 --- a/tools/debug/coroner/main.go +++ b/tools/debug/coroner/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/determaccount/main.go b/tools/debug/determaccount/main.go index 2c3c16d65e..3eec1f860c 100644 --- a/tools/debug/determaccount/main.go +++ b/tools/debug/determaccount/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/dumpblocks/main.go b/tools/debug/dumpblocks/main.go index 663945d83d..31a50631d7 100644 --- a/tools/debug/dumpblocks/main.go +++ b/tools/debug/dumpblocks/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/genconsensusconfig/main.go b/tools/debug/genconsensusconfig/main.go index edef949d3d..2de3f4e8f7 100644 --- a/tools/debug/genconsensusconfig/main.go +++ b/tools/debug/genconsensusconfig/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/logfilter/main.go b/tools/debug/logfilter/main.go index 11878ea470..25bf6feae4 100644 --- a/tools/debug/logfilter/main.go +++ b/tools/debug/logfilter/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/logfilter/main_test.go b/tools/debug/logfilter/main_test.go index 6b6902d62d..77d14beb53 100644 --- a/tools/debug/logfilter/main_test.go +++ b/tools/debug/logfilter/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/debug/transplanter/main.go b/tools/debug/transplanter/main.go index 1ee2a9c84e..0d2880412d 100644 --- a/tools/debug/transplanter/main.go +++ b/tools/debug/transplanter/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/misc/convertAddress.go b/tools/misc/convertAddress.go index 801c26106b..126afbe589 100644 --- a/tools/misc/convertAddress.go +++ b/tools/misc/convertAddress.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/bootstrap.go b/tools/network/bootstrap.go index 7558100a65..f04c67528c 100644 --- a/tools/network/bootstrap.go +++ b/tools/network/bootstrap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/bootstrap_test.go b/tools/network/bootstrap_test.go index 69ed39b2de..155615c704 100644 --- a/tools/network/bootstrap_test.go +++ b/tools/network/bootstrap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/cloudflare.go b/tools/network/cloudflare/cloudflare.go index 84f62f6533..a71e20b763 100644 --- a/tools/network/cloudflare/cloudflare.go +++ b/tools/network/cloudflare/cloudflare.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/createRecord.go b/tools/network/cloudflare/createRecord.go index a41dcbccc3..efaa840575 100644 --- a/tools/network/cloudflare/createRecord.go +++ b/tools/network/cloudflare/createRecord.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/deleteRecord.go b/tools/network/cloudflare/deleteRecord.go index 864a223dfc..a59ab75b9e 100644 --- a/tools/network/cloudflare/deleteRecord.go +++ b/tools/network/cloudflare/deleteRecord.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/helpers.go b/tools/network/cloudflare/helpers.go index e330d93309..cc5563837c 100644 --- a/tools/network/cloudflare/helpers.go +++ b/tools/network/cloudflare/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/listRecords.go b/tools/network/cloudflare/listRecords.go index 34efc71d53..e676b3e423 100644 --- a/tools/network/cloudflare/listRecords.go +++ b/tools/network/cloudflare/listRecords.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/updateRecord.go b/tools/network/cloudflare/updateRecord.go index 7add2d05be..269c48f11a 100644 --- a/tools/network/cloudflare/updateRecord.go +++ b/tools/network/cloudflare/updateRecord.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/cloudflare/zones.go b/tools/network/cloudflare/zones.go index c9a54a6dfd..8721ef1a74 100644 --- a/tools/network/cloudflare/zones.go +++ b/tools/network/cloudflare/zones.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/anchor.go b/tools/network/dnssec/anchor.go index 18d259aca3..a3f8c0b19c 100644 --- a/tools/network/dnssec/anchor.go +++ b/tools/network/dnssec/anchor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/anchor_test.go b/tools/network/dnssec/anchor_test.go index dbd7396e0f..6b574db8de 100644 --- a/tools/network/dnssec/anchor_test.go +++ b/tools/network/dnssec/anchor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/client.go b/tools/network/dnssec/client.go index 79cd79a9cf..71880db095 100644 --- a/tools/network/dnssec/client.go +++ b/tools/network/dnssec/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/client_test.go b/tools/network/dnssec/client_test.go index 7c2eede7db..f52008d441 100644 --- a/tools/network/dnssec/client_test.go +++ b/tools/network/dnssec/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config.go b/tools/network/dnssec/config.go index a5c9bbbfe4..6919e5618b 100644 --- a/tools/network/dnssec/config.go +++ b/tools/network/dnssec/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_test.go b/tools/network/dnssec/config_test.go index 8ad1ae511b..a84d30bb11 100644 --- a/tools/network/dnssec/config_test.go +++ b/tools/network/dnssec/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_unix.go b/tools/network/dnssec/config_unix.go index 74b3cb0f9d..72c9178720 100644 --- a/tools/network/dnssec/config_unix.go +++ b/tools/network/dnssec/config_unix.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_unix_test.go b/tools/network/dnssec/config_unix_test.go index 07a6a584bf..5a840257d6 100644 --- a/tools/network/dnssec/config_unix_test.go +++ b/tools/network/dnssec/config_unix_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/config_windows.go b/tools/network/dnssec/config_windows.go index 318f8f7a49..5772d10b1b 100644 --- a/tools/network/dnssec/config_windows.go +++ b/tools/network/dnssec/config_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/dialer.go b/tools/network/dnssec/dialer.go index b80277afd7..48896efcde 100644 --- a/tools/network/dnssec/dialer.go +++ b/tools/network/dnssec/dialer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/dnssec_test.go b/tools/network/dnssec/dnssec_test.go index 86c7239182..c9d558944f 100644 --- a/tools/network/dnssec/dnssec_test.go +++ b/tools/network/dnssec/dnssec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/relay-check/main.go b/tools/network/dnssec/relay-check/main.go index f82211e096..47b5cd21ff 100644 --- a/tools/network/dnssec/relay-check/main.go +++ b/tools/network/dnssec/relay-check/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/resolver.go b/tools/network/dnssec/resolver.go index 8900616f16..e4f5b9575f 100644 --- a/tools/network/dnssec/resolver.go +++ b/tools/network/dnssec/resolver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/sort.go b/tools/network/dnssec/sort.go index 2c8de2653a..5dcbeb94d7 100644 --- a/tools/network/dnssec/sort.go +++ b/tools/network/dnssec/sort.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/sort_test.go b/tools/network/dnssec/sort_test.go index f84ed118d3..9cf91cc997 100644 --- a/tools/network/dnssec/sort_test.go +++ b/tools/network/dnssec/sort_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/testHarness.go b/tools/network/dnssec/testHarness.go index 0f4c7f4161..c261547ddf 100644 --- a/tools/network/dnssec/testHarness.go +++ b/tools/network/dnssec/testHarness.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustchain.go b/tools/network/dnssec/trustchain.go index 15f69aea1d..38d4731e8d 100644 --- a/tools/network/dnssec/trustchain.go +++ b/tools/network/dnssec/trustchain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustedchain_test.go b/tools/network/dnssec/trustedchain_test.go index 6310e22ab4..16c8711a14 100644 --- a/tools/network/dnssec/trustedchain_test.go +++ b/tools/network/dnssec/trustedchain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustedzone.go b/tools/network/dnssec/trustedzone.go index a71385503e..44c8cb925e 100644 --- a/tools/network/dnssec/trustedzone.go +++ b/tools/network/dnssec/trustedzone.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/trustedzone_test.go b/tools/network/dnssec/trustedzone_test.go index 877a687415..a32d004b83 100644 --- a/tools/network/dnssec/trustedzone_test.go +++ b/tools/network/dnssec/trustedzone_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/util.go b/tools/network/dnssec/util.go index 48f5c9448c..020a645fee 100644 --- a/tools/network/dnssec/util.go +++ b/tools/network/dnssec/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/dnssec/util_test.go b/tools/network/dnssec/util_test.go index 2a77ad1c09..07b643b79c 100644 --- a/tools/network/dnssec/util_test.go +++ b/tools/network/dnssec/util_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/externalIP.go b/tools/network/externalIP.go index 121c43df32..8f8ab4f3f7 100644 --- a/tools/network/externalIP.go +++ b/tools/network/externalIP.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolveController.go b/tools/network/resolveController.go index b9b75fb7b7..0bd7dea79e 100644 --- a/tools/network/resolveController.go +++ b/tools/network/resolveController.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolveController_test.go b/tools/network/resolveController_test.go index db8a80573a..bdfd1ffc4c 100644 --- a/tools/network/resolveController_test.go +++ b/tools/network/resolveController_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolver.go b/tools/network/resolver.go index b823b17b20..ae9d123f2e 100644 --- a/tools/network/resolver.go +++ b/tools/network/resolver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/resolver_test.go b/tools/network/resolver_test.go index 6efe1bea45..d8efd59d41 100644 --- a/tools/network/resolver_test.go +++ b/tools/network/resolver_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/telemetryURIUpdateService.go b/tools/network/telemetryURIUpdateService.go index c6a5735573..66dd87dd0e 100644 --- a/tools/network/telemetryURIUpdateService.go +++ b/tools/network/telemetryURIUpdateService.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/network/telemetryURIUpdateService_test.go b/tools/network/telemetryURIUpdateService_test.go index 1014d28df3..751290720c 100644 --- a/tools/network/telemetryURIUpdateService_test.go +++ b/tools/network/telemetryURIUpdateService_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/algotmpl/extract.go b/tools/teal/algotmpl/extract.go index f542d6bce8..70a7825a56 100644 --- a/tools/teal/algotmpl/extract.go +++ b/tools/teal/algotmpl/extract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/algotmpl/main.go b/tools/teal/algotmpl/main.go index 824ab7ee9b..34760ac052 100644 --- a/tools/teal/algotmpl/main.go +++ b/tools/teal/algotmpl/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/dkey/dsign/main.go b/tools/teal/dkey/dsign/main.go index 5a4e2adfcd..4e7cc1927c 100644 --- a/tools/teal/dkey/dsign/main.go +++ b/tools/teal/dkey/dsign/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/teal/tealcut/main.go b/tools/teal/tealcut/main.go index 494392564b..61857d9ef3 100644 --- a/tools/teal/tealcut/main.go +++ b/tools/teal/tealcut/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/x-repo-types/typeAnalyzer/main.go b/tools/x-repo-types/typeAnalyzer/main.go index f5225a54a0..54c48a5f09 100644 --- a/tools/x-repo-types/typeAnalyzer/main.go +++ b/tools/x-repo-types/typeAnalyzer/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/x-repo-types/typeAnalyzer/main.tmpl b/tools/x-repo-types/typeAnalyzer/main.tmpl index 37ec0699ba..c7310ab252 100644 --- a/tools/x-repo-types/typeAnalyzer/main.tmpl +++ b/tools/x-repo-types/typeAnalyzer/main.tmpl @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go b/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go index 7aef6bde55..cde3894823 100644 --- a/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go +++ b/tools/x-repo-types/typeAnalyzer/typeAnalyzer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go b/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go index 632a38b520..b09f62d7cc 100644 --- a/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go +++ b/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/x-repo-types/xrt.go b/tools/x-repo-types/xrt.go index 016f50ff3a..8ea8a9f152 100644 --- a/tools/x-repo-types/xrt.go +++ b/tools/x-repo-types/xrt.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/tools/x-repo-types/xrt_test.go b/tools/x-repo-types/xrt_test.go index 4360b432d6..240e8a6ecd 100644 --- a/tools/x-repo-types/xrt_test.go +++ b/tools/x-repo-types/xrt_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/cmdUtils.go b/util/cmdUtils.go index 1382be178e..9b69cfe697 100644 --- a/util/cmdUtils.go +++ b/util/cmdUtils.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/codecs/json.go b/util/codecs/json.go index 8c2cebf087..5a248c9a78 100644 --- a/util/codecs/json.go +++ b/util/codecs/json.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/codecs/json_test.go b/util/codecs/json_test.go index 6bd4d53cd0..18745c7f6e 100644 --- a/util/codecs/json_test.go +++ b/util/codecs/json_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/condvar/timedwait.go b/util/condvar/timedwait.go index 7b275bfb3a..b4d5849736 100644 --- a/util/condvar/timedwait.go +++ b/util/condvar/timedwait.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/condvar/timedwait_test.go b/util/condvar/timedwait_test.go index fa7deef219..ba3e48c9a6 100644 --- a/util/condvar/timedwait_test.go +++ b/util/condvar/timedwait_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/dbpair.go b/util/db/dbpair.go index 0fa382063e..c5df75f5d7 100644 --- a/util/db/dbpair.go +++ b/util/db/dbpair.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/dbutil.go b/util/db/dbutil.go index c896c53771..a6e524464d 100644 --- a/util/db/dbutil.go +++ b/util/db/dbutil.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/dbutil_test.go b/util/db/dbutil_test.go index c35beccb23..e092a50481 100644 --- a/util/db/dbutil_test.go +++ b/util/db/dbutil_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/fullfsync_darwin.go b/util/db/fullfsync_darwin.go index 3261e7864a..2eff12f3f5 100644 --- a/util/db/fullfsync_darwin.go +++ b/util/db/fullfsync_darwin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/initialize.go b/util/db/initialize.go index fc11bc1aa4..f9f328b0a5 100644 --- a/util/db/initialize.go +++ b/util/db/initialize.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/initialize_test.go b/util/db/initialize_test.go index c0c8774b11..c0588d9cba 100644 --- a/util/db/initialize_test.go +++ b/util/db/initialize_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/interfaces.go b/util/db/interfaces.go index 938edc1031..5634b7a681 100644 --- a/util/db/interfaces.go +++ b/util/db/interfaces.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/perf_test.go b/util/db/perf_test.go index 5740ce2d47..c76f002fe1 100644 --- a/util/db/perf_test.go +++ b/util/db/perf_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/versioning.go b/util/db/versioning.go index 6104fe6b9c..51e28520fb 100644 --- a/util/db/versioning.go +++ b/util/db/versioning.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/db/versioning_test.go b/util/db/versioning_test.go index 74f048e30c..86634d4291 100644 --- a/util/db/versioning_test.go +++ b/util/db/versioning_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/execpool/backlog.go b/util/execpool/backlog.go index 9e95ebe653..44728d1d9e 100644 --- a/util/execpool/backlog.go +++ b/util/execpool/backlog.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/execpool/pool.go b/util/execpool/pool.go index d44657206f..caa7353ac7 100644 --- a/util/execpool/pool.go +++ b/util/execpool/pool.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/execpool/stream.go b/util/execpool/stream.go index 1ac61cb273..29ec4613f1 100644 --- a/util/execpool/stream.go +++ b/util/execpool/stream.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/execpool/stream_test.go b/util/execpool/stream_test.go index a6f3b17da4..8f40175fdd 100644 --- a/util/execpool/stream_test.go +++ b/util/execpool/stream_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/io.go b/util/io.go index 1ab09b3b68..f8290aec40 100644 --- a/util/io.go +++ b/util/io.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/io_test.go b/util/io_test.go index 5919051135..6f9f6dcfdd 100644 --- a/util/io_test.go +++ b/util/io_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/list.go b/util/list.go index be52459f7c..16e5a3816a 100644 --- a/util/list.go +++ b/util/list.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/list_test.go b/util/list_test.go index 4b87bef745..972cc28cb1 100644 --- a/util/list_test.go +++ b/util/list_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/counter.go b/util/metrics/counter.go index db0c6e6863..59b1bb2747 100644 --- a/util/metrics/counter.go +++ b/util/metrics/counter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/counterCommon.go b/util/metrics/counterCommon.go index dc187b3b48..2ce5468672 100644 --- a/util/metrics/counterCommon.go +++ b/util/metrics/counterCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/counter_test.go b/util/metrics/counter_test.go index 343f1f5fa8..1e1fa2e16b 100644 --- a/util/metrics/counter_test.go +++ b/util/metrics/counter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/gauge.go b/util/metrics/gauge.go index 593be0e9d5..6b43075ef7 100644 --- a/util/metrics/gauge.go +++ b/util/metrics/gauge.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/gauge_test.go b/util/metrics/gauge_test.go index afa0bbd593..098d20193e 100644 --- a/util/metrics/gauge_test.go +++ b/util/metrics/gauge_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/metrics.go b/util/metrics/metrics.go index cebece25fc..eb867729cf 100644 --- a/util/metrics/metrics.go +++ b/util/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/metrics_test.go b/util/metrics/metrics_test.go index 84cd4292fb..9b38f12fb2 100644 --- a/util/metrics/metrics_test.go +++ b/util/metrics/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/registry.go b/util/metrics/registry.go index 2f727aaabd..43078bb4c2 100644 --- a/util/metrics/registry.go +++ b/util/metrics/registry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/registryCommon.go b/util/metrics/registryCommon.go index 848ddc8369..742fae8dd6 100644 --- a/util/metrics/registryCommon.go +++ b/util/metrics/registryCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/registry_test.go b/util/metrics/registry_test.go index 17328f1cba..6072b1847d 100644 --- a/util/metrics/registry_test.go +++ b/util/metrics/registry_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/reporter.go b/util/metrics/reporter.go index 19aef0c369..20d2ea53e5 100644 --- a/util/metrics/reporter.go +++ b/util/metrics/reporter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/reporter_test.go b/util/metrics/reporter_test.go index 4c522a9b22..34c394562b 100755 --- a/util/metrics/reporter_test.go +++ b/util/metrics/reporter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/runtime.go b/util/metrics/runtime.go index 29d33e7814..4624abb250 100644 --- a/util/metrics/runtime.go +++ b/util/metrics/runtime.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/runtime_test.go b/util/metrics/runtime_test.go index 32908670d6..f62a471f6b 100644 --- a/util/metrics/runtime_test.go +++ b/util/metrics/runtime_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/service.go b/util/metrics/service.go index 5b1e395271..e579fd01a1 100644 --- a/util/metrics/service.go +++ b/util/metrics/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/serviceCommon.go b/util/metrics/serviceCommon.go index 947e9c4404..64e7696ce2 100644 --- a/util/metrics/serviceCommon.go +++ b/util/metrics/serviceCommon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/tagcounter.go b/util/metrics/tagcounter.go index ff3b8a732b..c2b5fcb9bb 100644 --- a/util/metrics/tagcounter.go +++ b/util/metrics/tagcounter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/metrics/tagcounter_test.go b/util/metrics/tagcounter_test.go index 8c5991e195..ec6ab09058 100644 --- a/util/metrics/tagcounter_test.go +++ b/util/metrics/tagcounter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/process.go b/util/process.go index 6a6d17d4ad..e7ce85ed92 100644 --- a/util/process.go +++ b/util/process.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/process_common.go b/util/process_common.go index 03af826b0c..4e74905670 100644 --- a/util/process_common.go +++ b/util/process_common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/process_windows.go b/util/process_windows.go index 263b997f5b..14470ce502 100644 --- a/util/process_windows.go +++ b/util/process_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/rateLimit.go b/util/rateLimit.go index c62264c32e..8db9763474 100644 --- a/util/rateLimit.go +++ b/util/rateLimit.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/rateLimit_test.go b/util/rateLimit_test.go index fd7a031408..2794c9a40a 100644 --- a/util/rateLimit_test.go +++ b/util/rateLimit_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/s3/fileIterator.go b/util/s3/fileIterator.go index b4f4ea82da..137af17415 100644 --- a/util/s3/fileIterator.go +++ b/util/s3/fileIterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/s3/s3Helper.go b/util/s3/s3Helper.go index 9fc6f06918..efad67d924 100644 --- a/util/s3/s3Helper.go +++ b/util/s3/s3Helper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/s3/s3Helper_test.go b/util/s3/s3Helper_test.go index 8ba636c2a1..3f7eeb5fbd 100644 --- a/util/s3/s3Helper_test.go +++ b/util/s3/s3Helper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/set.go b/util/set.go index a23f543dd6..6851299c46 100644 --- a/util/set.go +++ b/util/set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/sleep.go b/util/sleep.go index 3ad397bf0c..bfd67b9d2c 100644 --- a/util/sleep.go +++ b/util/sleep.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/sleep_linux.go b/util/sleep_linux.go index 1540fce736..a36840f181 100644 --- a/util/sleep_linux.go +++ b/util/sleep_linux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/sleep_linux_32.go b/util/sleep_linux_32.go index 2491349325..135ddecf76 100644 --- a/util/sleep_linux_32.go +++ b/util/sleep_linux_32.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/sleep_linux_64.go b/util/sleep_linux_64.go index cb6a7574c9..1c56a5d810 100644 --- a/util/sleep_linux_64.go +++ b/util/sleep_linux_64.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tar/tar.go b/util/tar/tar.go index 2ffc78c1bb..6b2640bab5 100644 --- a/util/tar/tar.go +++ b/util/tar/tar.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tar/untar.go b/util/tar/untar.go index fcbccc755f..1cad1beb59 100644 --- a/util/tar/untar.go +++ b/util/tar/untar.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tcpinfo.go b/util/tcpinfo.go index c387bba33f..2ac429a1a5 100644 --- a/util/tcpinfo.go +++ b/util/tcpinfo.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tcpinfo_darwin.go b/util/tcpinfo_darwin.go index ecb06ab668..50846e626f 100644 --- a/util/tcpinfo_darwin.go +++ b/util/tcpinfo_darwin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tcpinfo_linux.go b/util/tcpinfo_linux.go index 69fca11f97..a528a79b3d 100644 --- a/util/tcpinfo_linux.go +++ b/util/tcpinfo_linux.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tcpinfo_noop.go b/util/tcpinfo_noop.go index 7eecba7584..2e2641c3b4 100644 --- a/util/tcpinfo_noop.go +++ b/util/tcpinfo_noop.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/frozen.go b/util/timers/frozen.go index f4400c3a82..a8cf0636fe 100644 --- a/util/timers/frozen.go +++ b/util/timers/frozen.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/interface.go b/util/timers/interface.go index d217437e9e..8340d3d9de 100644 --- a/util/timers/interface.go +++ b/util/timers/interface.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/monotonic.go b/util/timers/monotonic.go index 96207f3950..9cf49e6ae3 100644 --- a/util/timers/monotonic.go +++ b/util/timers/monotonic.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/timers/monotonic_test.go b/util/timers/monotonic_test.go index 912f1b8425..1b661019e0 100644 --- a/util/timers/monotonic_test.go +++ b/util/timers/monotonic_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/tokens/tokens.go b/util/tokens/tokens.go index 1064a5863c..8c7de34153 100644 --- a/util/tokens/tokens.go +++ b/util/tokens/tokens.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/util.go b/util/util.go index f3699188c6..572dcab2cc 100644 --- a/util/util.go +++ b/util/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/util_windows.go b/util/util_windows.go index b485f8e253..a2a3bc3d1e 100644 --- a/util/util_windows.go +++ b/util/util_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/uuid/uuid.go b/util/uuid/uuid.go index e159ecf46e..84ce59c5c7 100644 --- a/util/uuid/uuid.go +++ b/util/uuid/uuid.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/uuid/uuid_test.go b/util/uuid/uuid_test.go index 17914af3cc..cdea4ca486 100644 --- a/util/uuid/uuid_test.go +++ b/util/uuid/uuid_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify diff --git a/util/watchdogStreamReader.go b/util/watchdogStreamReader.go index a54ca376bb..d5605571d6 100644 --- a/util/watchdogStreamReader.go +++ b/util/watchdogStreamReader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023 Algorand, Inc. +// Copyright (C) 2019-2024 Algorand, Inc. // This file is part of go-algorand // // go-algorand is free software: you can redistribute it and/or modify From e7aa0d4a785ee52f0194355912a1d9eae32a10fc Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 3 Jan 2024 10:59:58 -0500 Subject: [PATCH 058/218] Dryrun: Improve assembler error reporting (#5889) --- daemon/algod/api/server/v2/dryrun.go | 7 +++- daemon/algod/api/server/v2/dryrun_test.go | 42 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 7ce7c6abbf..9c3355cd10 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -89,7 +89,12 @@ func (dr *DryrunRequest) ExpandSources() error { for i, s := range dr.Sources { ops, err := logic.AssembleString(s.Source) if err != nil { - return fmt.Errorf("dryrun Source[%d]: %v", i, err) + if len(ops.Errors) <= 1 { + return fmt.Errorf("dryrun Source[%d]: %w", i, err) + } + var sb strings.Builder + ops.ReportMultipleErrors("", &sb) + return fmt.Errorf("dryrun Source[%d]: %d errors\n%s", i, len(ops.Errors), sb.String()) } switch s.FieldName { case "lsig": diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index ad0592f5fd..93177e488b 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -132,6 +132,48 @@ func logResponse(t *testing.T, response *model.DryrunResponse) { var dryrunProtoVersion protocol.ConsensusVersion = protocol.ConsensusFuture var dryrunMakeLedgerProto protocol.ConsensusVersion = "dryrunMakeLedgerProto" +func TestDryrunSources(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + goodSource := model.DryrunSource{ + AppIndex: 1007, + FieldName: "approv", + Source: `#pragma version 10 +int 1`, + } + badSource := model.DryrunSource{ + AppIndex: 1007, + FieldName: "approv", + Source: `#pragma version 10 +int 1 +pop +fake_opcode +int not_an_int`, + } + + dr := DryrunRequest{ + Sources: []model.DryrunSource{ + goodSource, + }, + Apps: []model.Application{ + { + Id: 1007, + }, + }, + } + var response model.DryrunResponse + + doDryrunRequest(&dr, &response) + require.Empty(t, response.Error) + + dr.Sources[0] = badSource + doDryrunRequest(&dr, &response) + require.Contains(t, response.Error, "dryrun Source[0]: 2 errors") + require.Contains(t, response.Error, "4: unknown opcode: fake_opcode") + require.Contains(t, response.Error, "5:4: unable to parse \"not_an_int\" as integer") +} + func TestDryrunLogicSig(t *testing.T) { partitiontest.PartitionTest(t) // {"txns":[{"lsig":{"l":"AiABASI="},"txn":{}}]} From 21eec2d39b4ac93d66f9e930e7b07d45f8248c24 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 5 Jan 2024 14:21:21 -0500 Subject: [PATCH 059/218] AVM: Adding sumhash and falcon_verify (#5599) --- data/transactions/logic/assembler_test.go | 15 ++- data/transactions/logic/crypto.go | 29 +++++ data/transactions/logic/crypto_test.go | 106 +++++++++++++++---- data/transactions/logic/doc.go | 5 +- data/transactions/logic/evalStateful_test.go | 2 + data/transactions/logic/opcodes.go | 12 ++- data/transactions/logic/teal.tmLanguage.json | 2 +- 7 files changed, 142 insertions(+), 29 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index e73129b432..3f609fd7ee 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -427,6 +427,13 @@ match_label1: pushbytess "1" "2" "1" ` +const stateProofNonsense = ` +pushbytes 0x0123456789abcd +sumhash512 +dup; dup +falcon_verify +` + const v8Nonsense = v7Nonsense + switchNonsense + frameNonsense + matchNonsense + boxNonsense const v9Nonsense = v8Nonsense @@ -438,7 +445,7 @@ const spliceNonsence = ` const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence -const v11Nonsense = v10Nonsense +const v11Nonsense = v10Nonsense + stateProofNonsense const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" @@ -460,7 +467,9 @@ const spliceCompiled = "d2d3" const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled -const V11Compiled = v10Compiled +const stateProofCompiled = "80070123456789abcd86494985" + +const V11Compiled = v10Compiled + stateProofCompiled var nonsense = map[uint64]string{ 1: v1Nonsense, @@ -541,7 +550,7 @@ func TestAssemble(t *testing.T) { } } -var experiments = []uint64{} +var experiments = []uint64{spOpcodesVersion} // TestExperimental forces a conscious choice to promote "experimental" opcode // groups. This will fail when we increment vFuture's LogicSigVersion. If we had diff --git a/data/transactions/logic/crypto.go b/data/transactions/logic/crypto.go index feb20725fc..c5c39b654c 100644 --- a/data/transactions/logic/crypto.go +++ b/data/transactions/logic/crypto.go @@ -28,6 +28,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/secp256k1" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-sumhash" "golang.org/x/crypto/sha3" ) @@ -70,6 +71,34 @@ func opSHA512_256(cx *EvalContext) error { return nil } +// Sumhash512 corresponds to the hash used in State Proofs +func opSumhash512(cx *EvalContext) error { + last := len(cx.Stack) - 1 + h := sumhash.New512(nil) + h.Write(cx.Stack[last].Bytes) + cx.Stack[last].Bytes = h.Sum(nil) + return nil +} + +func opFalconVerify(cx *EvalContext) error { + last := len(cx.Stack) - 1 // index of PK + prev := last - 1 // index of signature + pprev := prev - 1 // index of data + + var fv crypto.FalconVerifier + if len(cx.Stack[last].Bytes) != len(fv.PublicKey) { + return fmt.Errorf("invalid public key size %d != %d", len(cx.Stack[last].Bytes), len(fv.PublicKey)) + } + copy(fv.PublicKey[:], cx.Stack[last].Bytes) + + sig := crypto.FalconSignature(cx.Stack[prev].Bytes) + + err := fv.VerifyBytes(cx.Stack[pprev].Bytes, sig) + cx.Stack[pprev] = boolToSV(err == nil) + cx.Stack = cx.Stack[:prev] + return nil +} + // Msg is data meant to be signed and then verified with the // ed25519verify opcode. type Msg struct { diff --git a/data/transactions/logic/crypto_test.go b/data/transactions/logic/crypto_test.go index 7f539b8c7d..bb6ef59c56 100644 --- a/data/transactions/logic/crypto_test.go +++ b/data/transactions/logic/crypto_test.go @@ -55,6 +55,31 @@ byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567 testAccepts(t, progText, 1) } +func TestSumhash(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + /* tests extracted from test vector in go-algorand/sumhash */ + testVectors := []struct{ in, out string }{ + { + "", + "591591c93181f8f90054d138d6fa85b63eeeb416e6fd201e8375ba05d3cb55391047b9b64e534042562cc61944930c0075f906f16710cdade381ee9dd47d10a0", + }, + { + "a", + "ea067eb25622c633f5ead70ab83f1d1d76a7def8d140a587cb29068b63cb6407107aceecfdffa92579ed43db1eaa5bbeb4781223a6e07dd5b5a12d5e8bde82c6", + }, + { + "I think, therefore I am. – Rene Descartes.", + "2d4583cdb18710898c78ec6d696a86cc2a8b941bb4d512f9d46d96816d95cbe3f867c9b8bd31964406c847791f5669d60b603c9c4d69dadcb87578e613b60b7a", + }, + } + + for _, v := range testVectors { + testAccepts(t, fmt.Sprintf(`byte "%s"; sumhash512; byte 0x%s; ==`, v.in, v.out), 11) + } +} + func TestSHA3_256(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -169,6 +194,14 @@ byte 0x8e391a7b9cd75a99e8ebfe703036caebd9e91ae8339bd7e2abfb0f273eb8e972 byte 0x13e49a19378bbfa8d55ac81a35b87d7bae456c79fcf04a78803d8eb45b253fab byte 0xa2d237cd897ca70787abf04d2155c6dc2fbe26fd642e0472cd75c13dc919ef1a ecdsa_verify Secp256r1 +assert`, "int 1"}, + {"falcon_verify", "", ` +byte 0x62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd // msg +// public key +byte 0xba00a5222fbaa5e2a1a61f708198a4dbc3de94b60d925042d9fa5a299ebb4be27156b1d86a174df4939342f11b776dffb8a0e874714f23318ca9acb823e9aeb14a926ed5cf76e736faa0b22e4bdedf7910decd19329f353b926ae4b404653dbc6db1885c010052b94675d4cc209ef2cf3cfe910c4ef51b6af16d8c7ab6651e57934ab19c89f771058f389ad80474740c529d59a3ea9ab9db228415fb9315dee23e8c7229398c4b0a2b7c5d6eff6e7d8cf1a776ae37f6272082796c0b2a0af637f7ce8fa7f1675dfbd1766543cbf3f19544d635298ea1fafe96ad1bb06fcc6ae9ff9c34acacb88653555c37f2ad6c4eb408478b0d2b6269841243f29b18fa8e0d5050f4f93878e53aac466dc4eb5a7194cb2213c26a2b8c7ccea06f89af26ae85315454da1b15952be639bb94fe3e99236291c4a1edfbe9faf8f32589bf47eb536b28e2cfbdea799d9cf4c88ef85ae45d451e1ab3431c247b796cbf12e63b737cc4894ad7a204f680a449cbbd2e86deca1069b3592977bd8ac7c5b5e1c1b436cde65177b6e82b2e666117a8e37b58122d1a31307ca112311e665b32c68bd42531b4e6bc79957d3d865f6470b8213db8175e5c7115f4ad520a4711b12d9004e661346c4da4cb3e95954ac58e075a320b862a6a317e0988d8fc376fb14562773b9d35d5a44ba951d866a3a06ac93a55e1a26fa91718db49a53e78d9e61d6120dfadd2b4929579ac56ccaac0f8e704826b55b4ca6d8020e42a6e62b5e41708e2e6848cd047385fa1df4f51733df35dbee25c96c4176eae332ca4df31c695fff8be31b4be62e63c3e049483c89384fb1d802e58db5514a59eb96e527b202d0cf45dc760fa0439afbc661868b9408e67254c8cf7c689c50d2f29bccd59c71ea7b6dd368de68669fdf889ac1f8cd390ea17894dd0538ff6e7c740bbf03b4fe32ad66c483c823548eea84f85826da44016bd8cdf2315b07a96a9737ebc7cb244547be3f759bdf50b467552c58333ed7e61cde799346bccc29d5d377d9d5364c369ffd88a83f90a699b3622184436b518e9196524ac9b55385b39ec099d9c18386e06b9dcad2499ddb9673cb87c652209ee60511c9249f1b7ab2b948b5e8b9115c218d5b793d65b96e2fc9e2c6c40ba63791bb89d7d96c33536ad7e6668a85e52ec7e1450a69f25766deeaeb41bcd249394b8ab65a286312db461c363cebe431c4dd5fd3b6bb5d26ae2c597799f400abb3ba160522e2e6da5ebd170a45c9ce80b135a5b330656aab26399bcacd857a7f237dfd2b14ecbfbcaabc7291ba78fe19ac2ecf005b66bb9771bf64f090269a2341967e79702733dc617b469ac12123faeb4c70d6fffac25f9fcd7dbd12ca363985b9bd845e939e6caf328e2bf8e53725bae94fbe30bfdbbc21e584ba72d7badbc2a0915c9faf9c69ad3703cf99a16399b38df157be8ec3a78f20d24b2131f9c25b3c1da70fc6c0c7aa9e9da3108368ca6211dcfa4988b1ace3c28b15b0559224570fbe3cde364734c7a66a0525c1d41e26788cd1c8a3888f344c9385804364f8430ca7d22338cc941917da3dc47a00aae13e3e972af49940c8fa179574694e369a3d5e67db6c91bf843151ca0fff512d9c322c690063ae9bd671815e9d03b3a841952ce04683509e415b8d5aebfcdbd6bd55efbffb2463cf2b96ccb8650a6cee732c8d4ce6409b9a747317866759553f1c5bcc392c98d14a034ccaaa6df5723bb88c38e80 +// sig +byte 0x0a85ea3bb342a95a3941a4c2280c686729c76bc164092c0e203388460c556273e6f0a92640650c37e9d5b08fbd8d6bcca940acac9964e64a9e78bd28086b52898812264985e19c3d26318be2ec8852ca2ae2380746428cd08124cf792790d127d3dad09fe891cbadefef36269ae7d584b77ec428d794a6c3b7555956db00314d14a0aa14936830c8622623916639743b218243344224472240cfd158819190ede108394063c3df9c474eb16aa750e48663515d8229d3849670e30891142b632a6a282d915273a5f219d65ebe6b9e6c88170ac62c16a44895a950bfec82819221dab1358861bf0aa6b6342477016d50502a298840ddc42b3ade784b643c63c5e47993ada37dfdc0d56a1c7e4690b5a1d6485900b84f0b61425383b14d4b7ccc0abe8284a47a6f22050838b0482ad8ad389151c25e790ad670d5530f9b3dc518bb0a410f64346a74dc824238026daaa4ad97518d93670a48cf8f86ece593d23ab3a0d601d49a975db291f0d76263551e9f0b8a1b42396a27d9a122210330c692d5545d67c808b50560fc3d4933fa70c463513d7183e8aa091f34dd4426272620fe4b357deea710c687bb7a475d0ed0a40a26ae8f2a357e7a8fa5d5434050c1a36beaa7a90ee4db213a126db8151f2f4bbb4889d4e42bbd19f62dd7285def148071fb7f4f16b28c1d145d2e621fee275161a3d5b9319e7a59527c3d5c2838ef503e4166f2c22118b22bf80e8a1fc1bbbba00f231d2b1a8d3e592bdcc5fd40a2ecebb5ad27a51e7867715b54185a3e62951a5d808d80c31a59e6a3ca53a51eadc34c76dfd6aac22a6e805163b5e9ac8090869a9cd1e2972af7192bcd1da39c30f423ebc86d1976e8f52052262521d3b8ae7eb99d0ad623d811bac636f447e7dc9dcef6f52befd95861f1917116517b0e9b56a85967ab701ff8f1d4de443efce1b2a3d85b592df7a8c87814e8981575ef4e72757c5afa6bec4358e2f29966ad2830e4782f9a293351dfcaac1d0ca30ec1b5fd08a40a6e82938427a68641b96252a85443141c081982ba4d3c8ab05a1a545ea49c23ee07643ec5f013c2676db09cb834ef61817e615ad19c5829216026e5635dc13cad5ffb8bc267bf58d4ebbf100c3045e250c02c10772e96c580db049c80fdd3188e19ad893d16ac100052c557378416929319c9c262c21b768e6058a09b4e4800ae624c892117ec71504a283f558c623a212d048d5d401b00448b18ac25e1c99ab35d91f78badebcd651e86f3465ef99a0afa1721d2153e4a7b51d22b344a8dd102e7411abfe4bd5b8e2d62015edc08fc461fa90cfa666a9a42a0a86e11d6988913ba0259096cb846a1fd311c4cb693c4e3e1ed2ab57e2a5e0bd4616a79e22b28caa6d10dd09225e44bbdbfa1b7b23887055a90918220252777d5a620351cb013cc28346fc69d348165a39d03243a84a9c9bcd4d557a8e9607256baab893a0a5644520686be935e9ead84501f743a489a431cf10b8c27d3901c87b8771ce65e3130a7fe6ad62b709c23bbef1381b1ed49222f487db16af3c9d6779c01c986ea9f823be017fb8bce8e00f2b32840d54e8f656139a4c492257ee8743a8c5f51450c0366655e2b02d27619d07e556001430b04454891247813c8bc31bdee926d039a5038bfca8dc35e57789950442ad7ab3cfc031a8354bd9c462a37052d0b62066bcee0c292b890a71f4ea65895a7d837283404842c59f08414b20ec1b4fda6cc0c4d62216e8ead74ba90196168bc449a2050b442181ea57b915581bc387ed412e4cd5970fd0fb83c94fbbf960d05ffe6d0a26171c249809604a0b2b411e2d6622145c936e31258baf2b7d3c413a9a1d67bc4026d01b47a10b6c5b87f6a36ba1cedd681ca55b9c042bf9afcfcb636040793e08158dd877c49c16658f819129e26237427a1d80b941fbabb4abd4f1da0b6d428a59fbc450620eeb1651849e5972fb12e6dc8092a9fda70206a48d9dc2645641a147626350cf45b1a7d57724fcab0a594df7c023928a3c7a2fc3c9d33e9af10ae5ed282c475a611671d20d90752f2a28db48b7e5d9184212432fa948fbc885f866c93a0b7f510329aea4d53ecf9482f42974beaf289086afdb4797aa129d10639948f46a805ea4000cf1554505f4bd9d775d5894da115f5840913d5070c860b3a623eb261f5f928a31cbcec17c4274b5d1b28fdb231cc8f606c9dc324db5c12f97518fd03466541f7881762c25d711976c6d4f9271d29fa51dc263f650a32010343a51e7dab344e2f6d768864072ddb5df58486434998a280aad94886ea7a11132184e6274d4cd59a5deabf8a4dbbe29e9c234a52d3972608d0a3ea92a78e08531bb938384444246be5bc594ed4d06168e870924e8913f8242bd35f7c9d5ee238cb6db17496047acce0183f2d10a4cf2bbc8e39daf44e630393a0473b8983863b1998c17026ff35ec32a8058fd603ec369b80a94cb7b555cb469f6468de3909b21293b8d0a53a5c813d218d7c630f4d47bb1eb88253e6e1af721ba8a4453e +falcon_verify assert`, "int 1"}, {"vrf_verify", "", `byte 0x72 byte 0xae5b66bdf04b4c010bfe32b2fc126ead2107b697634f6f7337b9bff8785ee111200095ece87dde4dbe87343f6df3b107d91798c8a7eb1245d3bb9c5aafb093358c13e6ae1111a55717e895fd15f99f07 @@ -264,6 +297,52 @@ func TestEd25519VerifyBare(t *testing.T) { } } +func TestFalconVerify(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var s crypto.FalconSeed + fs, err := crypto.GenerateFalconSigner(s) + require.NoError(t, err) + + msg := "62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" + data, err := hex.DecodeString(msg) + require.NoError(t, err) + + yes := testProg(t, fmt.Sprintf(`arg 0; arg 1; byte 0x%s; falcon_verify`, + hex.EncodeToString(fs.PublicKey[:])), 11) + require.NoError(t, err) + no := testProg(t, fmt.Sprintf(`arg 0; arg 1; byte 0x%s; falcon_verify; !`, + hex.EncodeToString(fs.PublicKey[:])), 11) + require.NoError(t, err) + + for v := uint64(11); v <= AssemblerMaxVersion; v++ { + t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + yes.Program[0] = byte(v) + sig, err := fs.SignBytes(data) + require.NoError(t, err) + + var txn transactions.SignedTxn + txn.Lsig.Args = [][]byte{data[:], sig[:]} + testLogicBytes(t, yes.Program, defaultSigParams(txn)) + testLogicBytes(t, no.Program, defaultSigParams(txn), "REJECT") + + // short sig will fail + txn.Lsig.Args[1] = sig[1:] + testLogicBytes(t, yes.Program, defaultSigParams(txn), "REJECT") + testLogicBytes(t, no.Program, defaultSigParams(txn)) + + // flip a bit and it should not pass + msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" + data1, err := hex.DecodeString(msg1) + require.NoError(t, err) + txn.Lsig.Args = [][]byte{data1, sig[:]} + testLogicBytes(t, yes.Program, defaultSigParams(txn), "REJECT") + testLogicBytes(t, no.Program, defaultSigParams(txn)) + }) + } +} + func keyToByte(tb testing.TB, b *big.Int) []byte { k := make([]byte, 32) require.NotPanics(tb, func() { @@ -636,26 +715,13 @@ int ` + fmt.Sprintf("%d", testLogicBudget-2500-8) + ` testAccepts(t, source, fidoVersion) } -func BenchmarkHash(b *testing.B) { - for _, hash := range []string{"sha256", "keccak256", "sha512_256"} { - b.Run(hash+"-0w", func(b *testing.B) { // hash 0 bytes - benchmarkOperation(b, "", "byte 0x; "+hash+"; pop", "int 1") - }) - b.Run(hash+"-32", func(b *testing.B) { // hash 32 bytes - benchmarkOperation(b, "int 32; bzero", hash, "pop; int 1") - }) - b.Run(hash+"-128", func(b *testing.B) { // hash 128 bytes - benchmarkOperation(b, "int 32; bzero", - "dup; concat; dup; concat;"+hash, "pop; int 1") - }) - b.Run(hash+"-512", func(b *testing.B) { // hash 512 bytes - benchmarkOperation(b, "int 32; bzero", - "dup; concat; dup; concat; dup; concat; dup; concat;"+hash, "pop; int 1") - }) - b.Run(hash+"-4096", func(b *testing.B) { // hash 4k bytes - benchmarkOperation(b, "int 32; bzero", - "dup; concat; dup; concat; dup; concat; dup; concat; dup; concat; dup; concat; dup; concat;"+hash, "pop; int 1") - }) +func BenchmarkHashes(b *testing.B) { + for _, hash := range []string{"sha256", "keccak256" /* skip, same as keccak "sha3_256", */, "sha512_256", "sumhash512"} { + for _, size := range []int{0, 32, 128, 512, 1024, 4096} { + b.Run(hash+"-"+strconv.Itoa(size), func(b *testing.B) { + benchmarkOperation(b, "", fmt.Sprintf("int %d; bzero; %s; pop", size, hash), "int 1") + }) + } } } diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index cfaadebdf3..fde870a9d4 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -38,6 +38,9 @@ var opDescByName = map[string]OpDesc{ "sha512_256": {"SHA512_256 hash of value A, yields [32]byte", "", nil}, "sha3_256": {"SHA3_256 hash of value A, yields [32]byte", "", nil}, + "sumhash512": {"sumhash512 of value A, yields [64]byte", "", nil}, + "falcon_verify": {"for (data A, compressed-format signature B, pubkey C) verify the signature of data against the pubkey", "", nil}, + "ed25519verify": {"for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey => {0 or 1}", "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", nil}, "ed25519verify_bare": {"for (data A, signature B, pubkey C) verify the signature of the data against the pubkey => {0 or 1}", "", nil}, "ecdsa_verify": {"for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1}", "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", []string{"curve index"}}, @@ -349,7 +352,7 @@ var OpGroups = map[string][]string{ "Byte Array Manipulation": {"getbit", "setbit", "getbyte", "setbyte", "concat", "len", "substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64", "replace2", "replace3", "base64_decode", "json_ref"}, "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%", "bsqrt"}, "Byte Array Logic": {"b|", "b&", "b^", "b~"}, - "Cryptography": {"sha256", "keccak256", "sha512_256", "sha3_256", "ed25519verify", "ed25519verify_bare", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "vrf_verify", "ec_add", "ec_scalar_mul", "ec_pairing_check", "ec_multi_scalar_mul", "ec_subgroup_check", "ec_map_to"}, + "Cryptography": {"sha256", "keccak256", "sha512_256", "sha3_256", "sumhash512", "falcon_verify", "ed25519verify", "ed25519verify_bare", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "vrf_verify", "ec_add", "ec_scalar_mul", "ec_pairing_check", "ec_multi_scalar_mul", "ec_subgroup_check", "ec_map_to"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "pushints", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "pushbytess", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "popn", "dup", "dup2", "dupn", "dig", "bury", "cover", "uncover", "frame_dig", "frame_bury", "swap", "select", "assert", "callsub", "proto", "retsub", "switch", "match"}, "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log", "block"}, diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index b0c6196e39..fe30d2594a 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -3098,6 +3098,8 @@ func TestReturnTypes(t *testing.T) { "err": true, "return": true, + // these have unusual input size requirements not encoded in proto + "falcon_verify": true, "ecdsa_pk_decompress": true, "frame_dig": true, // would need a "proto" subroutine diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 650f26ed9b..18af2af1b2 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -77,6 +77,8 @@ const sharedResourcesVersion = 9 // apps can access resources from other transac const pairingVersion = 10 // bn256 opcodes. will add bls12-381, and unify the available opcodes. const spliceVersion = 10 // box splicing/resizing +const spOpcodesVersion = 11 // falcon_verify, sumhash512 + // Unlimited Global Storage opcodes const boxVersion = 8 // box_* @@ -503,9 +505,9 @@ var OpSpecs = []OpSpec{ is currently a useful pattern that requires hashes on long slices to creating logicsigs in apps. - {0x01, "sha256", opSHA256, proto("b:b"), unlimitedStorage, costByLength(12, 6, 8)}, - {0x02, "keccak256", opKeccak256, proto("b:b"), unlimitedStorage, costByLength(58, 4, 8)}, - {0x03, "sha512_256", opSHA512_256, proto("b:b"), 7, unlimitedStorage, costByLength(17, 5, 8)}, + {0x01, "sha256", opSHA256, proto("b:b"), ?, costByLength(...)}, + {0x02, "keccak256", opKeccak256, proto("b:b"), ?, costByLength(...)}, + {0x03, "sha512_256", opSHA512_256, proto("b:b"), ?, costByLength(...)}, */ {0x04, "ed25519verify", opEd25519Verify, proto("b63:T"), 1, costly(1900).only(ModeSig)}, @@ -647,6 +649,8 @@ var OpSpecs = []OpSpec{ {0x83, "pushints", opPushInts, proto(":", "", "[N items]").stackExplain(opPushIntsStackChange), 8, constants(asmPushInts, checkIntImmArgs, "uint ...", immInts).typed(typePushInts).trust()}, {0x84, "ed25519verify_bare", opEd25519VerifyBare, proto("b63:T"), 7, costly(1900)}, + {0x85, "falcon_verify", opFalconVerify, proto("bbb:T"), spOpcodesVersion, costly(1700)}, // dynamic for internal hash? + {0x86, "sumhash512", opSumhash512, proto("b:6"), spOpcodesVersion, costByLength(150, 7, 4, 0)}, // "Function oriented" {0x88, "callsub", opCallSub, proto(":"), 4, detBranch()}, @@ -669,7 +673,7 @@ var OpSpecs = []OpSpec{ {0x97, "divw", opDivw, proto("iii:i"), 6, detDefault()}, {0x98, "sha3_256", opSHA3_256, proto("b:b"), 7, costly(130)}, /* Will end up following keccak256 - - {0x98, "sha3_256", opSHA3_256, proto("b:b"), unlimitedStorage, costByLength(58, 4, 8)},}, + {0x98, "sha3_256", opSHA3_256, proto("b:b"), ?, costByLength(...)},}, */ // Byteslice math. diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index e414922e81..915957f7f0 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -76,7 +76,7 @@ }, { "name": "keyword.operator.teal", - "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|keccak256|sha256|sha3_256|sha512_256|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" + "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|falcon_verify|keccak256|sha256|sha3_256|sha512_256|sumhash512|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" } ] }, From 71fff6dd6a2de0a1b5efeae39dd09ef8f76c8663 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:13:29 -0500 Subject: [PATCH 060/218] Algocfg: Introduce new archival node algocfg profile. (#5893) --- cmd/algocfg/profileCommand.go | 13 +++++++++++++ cmd/algocfg/profileCommand_test.go | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 1a28587ee7..06ae4ff61a 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -77,11 +77,24 @@ var ( }, } + archival = configUpdater{ + description: "Store the full chain history and support catchup.", + updateFunc: func(cfg config.Local) config.Local { + cfg.Archival = true + cfg.EnableLedgerService = true + cfg.EnableBlockService = true + cfg.NetAddress = ":4160" + cfg.EnableGossipService = false + return cfg + }, + } + // profileNames are the supported pre-configurations of config values profileNames = map[string]configUpdater{ "participation": participation, "conduit": conduit, "relay": relay, + "archival": archival, "development": development, } diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go index afe9fd4f8e..d8bf715534 100644 --- a/cmd/algocfg/profileCommand_test.go +++ b/cmd/algocfg/profileCommand_test.go @@ -51,4 +51,15 @@ func Test_getConfigForArg(t *testing.T) { require.NoError(t, err) require.True(t, cfg.DisableAPIAuth) }) + + t.Run("valid config test archival node", func(t *testing.T) { + t.Parallel() + cfg, err := getConfigForArg("archival") + require.NoError(t, err) + require.True(t, cfg.Archival) + require.True(t, cfg.EnableLedgerService) + require.True(t, cfg.EnableBlockService) + require.Equal(t, ":4160", cfg.NetAddress) + require.False(t, cfg.EnableGossipService) + }) } From a0517cbeddf663922968e208794ecc8eb8021eea Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 9 Jan 2024 12:07:35 -0500 Subject: [PATCH 061/218] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index 573541ac97..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/config/version.go b/config/version.go index ea7d417b2f..272cff9f93 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 21 +const VersionMinor = 22 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From d383a911847648140e15994d21a151b7cafdce0c Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 11 Jan 2024 18:46:42 -0500 Subject: [PATCH 062/218] Releases Page: remove indexer and point to github (#5896) --- .../releases_page/generate_releases_page.py | 4 ++-- .../release/mule/deploy/releases_page/html.tpl | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) 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 07d7ce70a0..d0b580cd78 100755 --- a/scripts/release/mule/deploy/releases_page/generate_releases_page.py +++ b/scripts/release/mule/deploy/releases_page/generate_releases_page.py @@ -24,7 +24,7 @@ # Nit: should be styles_file styles_url = "releases_page.css" # May want to call these channels instead -tokens = ["stable", "beta", "indexer"] +tokens = ["stable", "beta"] def get_stage_release_set(response): @@ -201,7 +201,7 @@ def main(): channels = {} # Should use tokens array instead - for channel in ["stable", "beta", "indexer"]: + for channel in ["stable", "beta"]: # Fetch contents of e.g. s3://algorand-dev-deb-repo/releases/beta/ # Note: MaxKeys will limit to last 100 releases, which is more than # enough. Consider dropping this to 2. diff --git a/scripts/release/mule/deploy/releases_page/html.tpl b/scripts/release/mule/deploy/releases_page/html.tpl index 3c8f78bc9b..d51ba241dd 100644 --- a/scripts/release/mule/deploy/releases_page/html.tpl +++ b/scripts/release/mule/deploy/releases_page/html.tpl @@ -13,6 +13,15 @@

The public key for verifying RPMs is https://releases.algorand.com/rpm/rpm_algorand.pub

The public key for verifying binaries out of our CI builds is https://releases.algorand.com/dev_ci_build.pub

+

Indexer/Conduit

+ +Use the CI Build key above to verify these binaries. + + +
@@ -30,13 +39,6 @@
-
-

Indexer releases

- -{indexer} -
FileBytesGPG Signature
-
- From bb86c364a77b8aa29ea83985138d8cc297bbb49f Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 17 Jan 2024 11:02:26 -0500 Subject: [PATCH 063/218] API: Make maps the right size from the start (#5906) --- data/pools/statusCache.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/pools/statusCache.go b/data/pools/statusCache.go index ed11451933..c8977e3fc6 100644 --- a/data/pools/statusCache.go +++ b/data/pools/statusCache.go @@ -52,7 +52,7 @@ func (sc *statusCache) check(txid transactions.Txid) (tx transactions.SignedTxn, func (sc *statusCache) put(tx transactions.SignedTxn, txErr string) { if len(sc.cur) >= sc.sz { sc.prev = sc.cur - sc.cur = map[transactions.Txid]statusCacheEntry{} + sc.cur = make(map[transactions.Txid]statusCacheEntry, sc.sz) } sc.cur[tx.ID()] = statusCacheEntry{ @@ -62,6 +62,6 @@ func (sc *statusCache) put(tx transactions.SignedTxn, txErr string) { } func (sc *statusCache) reset() { - sc.cur = map[transactions.Txid]statusCacheEntry{} - sc.prev = map[transactions.Txid]statusCacheEntry{} + sc.cur = make(map[transactions.Txid]statusCacheEntry, sc.sz) + sc.prev = nil } From 2086425a2f9e0140c9a86631471be49d5df52dad Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Wed, 17 Jan 2024 12:52:16 -0500 Subject: [PATCH 064/218] Docs: Updates to SECURITY.md (#5907) --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 4438c3e9a7..670e493ea1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,7 +1,7 @@ # Vulnerability Disclosures -Algorand takes the security of the platform and of its users very seriously. We recognize the important role of external security researchers and developers in helping keep our community safe. As with most security reward programs, we ask that you use common sense when looking for security bugs. Vulnerabilities must be disclosed to us privately with reasonable time to respond, and avoid compromise of other users and accounts, or loss of funds that are not your own. We do not reward denial of service, spam, or social engineering vulnerabilities. +Algorand takes the security of the platform and of its users very seriously. We recognize the important role of external security researchers and developers in helping keep our community safe. Vulnerabilities must be disclosed to us privately with reasonable time to respond, and avoid compromise of other users and accounts, or loss of funds that are not your own. We do not reward denial of service, spam, or social engineering vulnerabilities. If you believe that you have found a security vulnerability you may disclose it here: -https://immunefi.com/bounty/algorand/ +https://algorandtechnologies.com/contact/security/ From 5e5c16a7b4e6c40c9e57dcce65b51118755592ba Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 17 Jan 2024 13:21:16 -0500 Subject: [PATCH 065/218] api: optimize /transactions/pending/{txid} endpoint (#5891) --- ledger/ledger.go | 9 +++++ ledger/txtail.go | 50 ++++++++++++++++++++------- ledger/txtail_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++- node/node.go | 22 +++++++++++- 4 files changed, 146 insertions(+), 15 deletions(-) diff --git a/ledger/ledger.go b/ledger/ledger.go index 9313f55e68..d5436ad5d4 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -657,6 +657,15 @@ func (l *Ledger) CheckDup(currentProto config.ConsensusParams, current basics.Ro return l.txTail.checkDup(currentProto, current, firstValid, lastValid, txid, txl) } +// CheckConfirmedTail checks if a transaction txid happens to have LastValid greater than the current round at the time of calling and has been already committed to the ledger. +// If both conditions are met it returns true. +// This function could be used as filter to check if a transaction is committed to the ledger, and no extra checks needed if it says true. +// +// Note, this cannot be used to check if transaction happened or not in past MaxTxnLife rounds. +func (l *Ledger) CheckConfirmedTail(txid transactions.Txid) (basics.Round, bool) { + return l.txTail.checkConfirmed(txid) +} + // Latest returns the latest known block round added to the ledger. func (l *Ledger) Latest() basics.Round { return l.blockQ.latest() diff --git a/ledger/txtail.go b/ledger/txtail.go index e1e85fee66..129fbb3985 100644 --- a/ledger/txtail.go +++ b/ledger/txtail.go @@ -80,7 +80,9 @@ type txTail struct { // lastValid, recent, lowWaterMark, roundTailHashes, roundTailSerializedDeltas and blockHeaderData. tailMu deadlock.RWMutex - lastValid map[basics.Round]map[transactions.Txid]struct{} // map tx.LastValid -> tx confirmed set + // lastValid allows looking up all of the transactions that expire in a given round. + // The map for an expiration round gives the round the transaction was originally confirmed, so it can be found for the /pending endpoint. + lastValid map[basics.Round]map[transactions.Txid]uint16 // map tx.LastValid -> tx confirmed map: txid -> (last valid - confirmed) delta // duplicate detection queries with LastValid before // lowWaterMark are not guaranteed to succeed @@ -115,14 +117,18 @@ func (t *txTail) loadFromDisk(l ledgerForTracker, dbRound basics.Round) error { } t.lowWaterMark = l.Latest() - t.lastValid = make(map[basics.Round]map[transactions.Txid]struct{}) + t.lastValid = make(map[basics.Round]map[transactions.Txid]uint16) t.recent = make(map[basics.Round]roundLeases) // the lastValid is a temporary map used during the execution of // loadFromDisk, allowing us to construct the lastValid maps in their // optimal size. This would ensure that upon startup, we don't preallocate // more memory than we truly need. - lastValid := make(map[basics.Round][]transactions.Txid) + type lastValidEntry struct { + rnd basics.Round + txid transactions.Txid + } + lastValid := make(map[basics.Round][]lastValidEntry) // the roundTailHashes and blockHeaderData need a single element to start with // in order to allow lookups on zero offsets when they are empty (new database) @@ -153,16 +159,16 @@ func (t *txTail) loadFromDisk(l ledgerForTracker, dbRound basics.Round) error { list := lastValid[txTailRound.LastValid[i]] // if the list reached capacity, resize. if len(list) == cap(list) { - var newList []transactions.Txid + var newList []lastValidEntry if cap(list) == 0 { - newList = make([]transactions.Txid, 0, initialLastValidArrayLen) + newList = make([]lastValidEntry, 0, initialLastValidArrayLen) } else { - newList = make([]transactions.Txid, len(list), len(list)*2) + newList = make([]lastValidEntry, len(list), len(list)*2) } copy(newList[:], list[:]) list = newList } - list = append(list, txTailRound.TxnIDs[i]) + list = append(list, lastValidEntry{txTailRound.Hdr.Round, txTailRound.TxnIDs[i]}) lastValid[txTailRound.LastValid[i]] = list } } @@ -173,11 +179,15 @@ func (t *txTail) loadFromDisk(l ledgerForTracker, dbRound basics.Round) error { // add all the entries in roundsLastValids to their corresponding map entry in t.lastValid for lastValid, list := range lastValid { - lastValueMap := make(map[transactions.Txid]struct{}, len(list)) - for _, id := range list { - lastValueMap[id] = struct{}{} + lastValidMap := make(map[transactions.Txid]uint16, len(list)) + for _, entry := range list { + if lastValid < entry.rnd { + return fmt.Errorf("txTail: invalid lastValid %d / rnd %d for txid %s", lastValid, entry.rnd, entry.txid) + } + deltaR := uint16(lastValid - entry.rnd) + lastValidMap[entry.txid] = deltaR } - t.lastValid[lastValid] = lastValueMap + t.lastValid[lastValid] = lastValidMap } if enableTxTailHashes { @@ -210,9 +220,10 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { for txid, txnInc := range delta.Txids { if _, ok := t.lastValid[txnInc.LastValid]; !ok { - t.lastValid[txnInc.LastValid] = make(map[transactions.Txid]struct{}) + t.lastValid[txnInc.LastValid] = make(map[transactions.Txid]uint16) } - t.lastValid[txnInc.LastValid][txid] = struct{}{} + deltaR := uint16(txnInc.LastValid - blk.BlockHeader.Round) + t.lastValid[txnInc.LastValid][txid] = deltaR tail.TxnIDs[txnInc.Intra] = txid tail.LastValid[txnInc.Intra] = txnInc.LastValid @@ -381,6 +392,19 @@ func (t *txTail) checkDup(proto config.ConsensusParams, current basics.Round, fi return nil } +// checkConfirmed test to see if the given transaction id already exists. +func (t *txTail) checkConfirmed(txid transactions.Txid) (basics.Round, bool) { + t.tailMu.RLock() + defer t.tailMu.RUnlock() + + for lastValidRound, lastValid := range t.lastValid { + if deltaR, confirmed := lastValid[txid]; confirmed { + return lastValidRound - basics.Round(deltaR), true + } + } + return 0, false +} + func (t *txTail) recentTailHash(offset uint64, retainSize uint64) (crypto.Digest, error) { // prepare a buffer to hash. buffer := make([]byte, (retainSize)*crypto.DigestSize) diff --git a/ledger/txtail_test.go b/ledger/txtail_test.go index fa3473fcaf..8eb9d49901 100644 --- a/ledger/txtail_test.go +++ b/ledger/txtail_test.go @@ -112,13 +112,15 @@ func TestTxTailCheckdup(t *testing.T) { type txTailTestLedger struct { Ledger protoVersion protocol.ConsensusVersion + blocks map[basics.Round]bookkeeping.Block } const testTxTailValidityRange = 200 const testTxTailTxnPerRound = 150 +const testTxTailExtraRounds = 10 func (t *txTailTestLedger) Latest() basics.Round { - return basics.Round(config.Consensus[t.protoVersion].MaxTxnLife + 10) + return basics.Round(config.Consensus[t.protoVersion].MaxTxnLife + testTxTailExtraRounds) } func (t *txTailTestLedger) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { @@ -130,6 +132,10 @@ func (t *txTailTestLedger) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, er } func (t *txTailTestLedger) Block(r basics.Round) (bookkeeping.Block, error) { + if bkl, found := t.blocks[r]; found { + return bkl, nil + } + blk := bookkeeping.Block{ BlockHeader: bookkeeping.BlockHeader{ UpgradeState: bookkeeping.UpgradeState{ @@ -142,6 +148,10 @@ func (t *txTailTestLedger) Block(r basics.Round) (bookkeeping.Block, error) { for i := range blk.Payset { blk.Payset[i] = makeTxTailTestTransaction(r, i) } + if t.blocks == nil { + t.blocks = make(map[basics.Round]bookkeeping.Block) + } + t.blocks[r] = blk return blk, nil } @@ -330,6 +340,74 @@ func TestTxTailDeltaTracking(t *testing.T) { } } +func TestTxTailCheckConfirmed(t *testing.T) { + partitiontest.PartitionTest(t) + + var ledger txTailTestLedger + txtail := txTail{} + protoVersion := protocol.ConsensusCurrentVersion + proto := config.Consensus[protoVersion] + require.NoError(t, ledger.initialize(t, protoVersion)) + require.NoError(t, txtail.loadFromDisk(&ledger, ledger.Latest())) + + // ensure block retrieval from txTailTestLedger works + startRound := ledger.Latest() - basics.Round(proto.MaxTxnLife) + 1 + b1, err := ledger.Block(startRound) + require.NoError(t, err) + b2, err := ledger.Block(startRound) + require.NoError(t, err) + require.Equal(t, b1, b2) + + // check all txids in blocks are in txTail as well + // note, txtail does not store txids for transactions with lastValid < ledger.Latest() + for i := ledger.Latest() - testTxTailValidityRange + 1; i < ledger.Latest(); i++ { + blk, err := ledger.Block(i) + require.NoError(t, err) + for _, txn := range blk.Payset { + confirmedAt, found := txtail.checkConfirmed(txn.Txn.ID()) + require.True(t, found, "failed to find txn at round %d (startRound=%d, latest=%d)", i, startRound, ledger.Latest()) + require.Equal(t, basics.Round(i), confirmedAt) + } + } + + rnd := ledger.Latest() + 1 + lv := basics.Round(rnd + 50) + blk := bookkeeping.Block{ + BlockHeader: bookkeeping.BlockHeader{ + Round: rnd, + TimeStamp: int64(rnd << 10), + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: protoVersion, + }, + }, + Payset: make(transactions.Payset, 1), + } + sender := &basics.Address{} + sender[0] = byte(rnd) + sender[1] = byte(rnd >> 8) + sender[2] = byte(rnd >> 16) + blk.Payset[0].Txn.Sender = *sender + blk.Payset[0].Txn.FirstValid = rnd + blk.Payset[0].Txn.LastValid = lv + deltas := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) + deltas.Txids[blk.Payset[0].Txn.ID()] = ledgercore.IncludedTransactions{ + LastValid: lv, + Intra: 0, + } + deltas.AddTxLease(ledgercore.Txlease{Sender: blk.Payset[0].Txn.Sender, Lease: blk.Payset[0].Txn.Lease}, basics.Round(rnd+50)) + + txtail.newBlock(blk, deltas) + txtail.committedUpTo(basics.Round(rnd)) + + confirmedAt, found := txtail.checkConfirmed(blk.Payset[0].Txn.ID()) + require.True(t, found) + require.Equal(t, basics.Round(rnd), confirmedAt) + + confirmedAt, found = txtail.checkConfirmed(transactions.Txid{}) + require.False(t, found) + require.Equal(t, basics.Round(0), confirmedAt) +} + // BenchmarkTxTailBlockHeaderCache adds 2M random blocks by calling // newBlock and postCommit on txTail tracker, and reports memory allocations func BenchmarkTxTailBlockHeaderCache(b *testing.B) { diff --git a/node/node.go b/node/node.go index 75d9bcfa27..f59bd67dab 100644 --- a/node/node.go +++ b/node/node.go @@ -653,6 +653,25 @@ func (node *AlgorandFullNode) GetPendingTransaction(txID transactions.Txid) (res // Keep looking in the ledger. } + // quick check for confirmed transactions with LastValid in future + // this supposed to cover most of the cases where REST checks for the most recent txns + if r, confirmed := node.ledger.CheckConfirmedTail(txID); confirmed { + tx, foundBlk, err := node.ledger.LookupTxid(txID, r) + if err == nil && foundBlk { + return TxnWithStatus{ + Txn: tx.SignedTxn, + ConfirmedRound: r, + ApplyData: tx.ApplyData, + }, true + } + } + // if found in the pool and not in the tail then return without looking into blocks + // because the check appears to be too early + if found { + return res, found + } + + // fallback to blocks lookup var maxLife basics.Round latest := node.ledger.Latest() proto, err := node.ledger.ConsensusParams(latest) @@ -688,6 +707,7 @@ func (node *AlgorandFullNode) GetPendingTransaction(txID transactions.Txid) (res if err != nil || !found { continue } + return TxnWithStatus{ Txn: tx.SignedTxn, ConfirmedRound: r, @@ -696,7 +716,7 @@ func (node *AlgorandFullNode) GetPendingTransaction(txID transactions.Txid) (res } // Return whatever we found in the pool (if anything). - return + return res, found } // Status returns a StatusReport structure reporting our status as Active and with our ledger's LastRound From 434dca0f02d48d30af1d42eb223039d9e16f0bab Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 17 Jan 2024 14:20:29 -0500 Subject: [PATCH 066/218] AVM: report structure txn failure info (#5875) --- daemon/algod/api/client/restClient.go | 24 +- daemon/algod/api/server/v2/utils.go | 8 +- data/basics/serr.go | 132 +++++++++++ data/basics/serr_test.go | 135 +++++++++++ data/transactions/logic/eval.go | 131 +++++++++-- data/transactions/logic/evalAppTxn_test.go | 4 +- data/transactions/logic/evalStateful_test.go | 222 +++++++++++++----- data/transactions/logic/eval_test.go | 130 +++++++--- data/transactions/logic/export_test.go | 2 +- data/transactions/logic/ledger_test.go | 2 +- data/transactions/logic/resources_test.go | 2 +- ledger/simulation/simulation_eval_test.go | 2 +- .../goal/expect/goalAppAccountAddressTest.exp | 0 .../goal/expect/statefulTealCreateAppTest.exp | 0 .../cli/goal/expect/tealConsensusTest.exp | 0 test/e2e-go/restAPI/other/appsRestAPI_test.go | 14 +- test/scripts/e2e_subs/avm-failure-info.py | 78 ++++++ test/scripts/e2e_subs/goal/goal.py | 13 + 18 files changed, 758 insertions(+), 141 deletions(-) create mode 100644 data/basics/serr.go create mode 100644 data/basics/serr_test.go mode change 100644 => 100755 test/e2e-go/cli/goal/expect/goalAppAccountAddressTest.exp mode change 100755 => 100644 test/e2e-go/cli/goal/expect/statefulTealCreateAppTest.exp mode change 100755 => 100644 test/e2e-go/cli/goal/expect/tealConsensusTest.exp create mode 100755 test/scripts/e2e_subs/avm-failure-info.py diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index cf25773fd9..f6494fa380 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -72,6 +72,7 @@ type HTTPError struct { StatusCode int Status string ErrorString string + Data map[string]any } // Error formats an error string. @@ -120,24 +121,11 @@ func extractError(resp *http.Response) error { decodeErr := json.Unmarshal(errorBuf, &errorJSON) var errorString string + var data map[string]any if decodeErr == nil { - if errorJSON.Data == nil { - // There's no additional data, so let's just use the message - errorString = errorJSON.Message - } else { - // There's additional data, so let's re-encode the JSON response to show everything. - // We do this because the original response is likely encoded with escapeHTML=true, but - // since this isn't a webpage that extra encoding is not preferred. - var buffer strings.Builder - enc := json.NewEncoder(&buffer) - enc.SetEscapeHTML(false) - encErr := enc.Encode(errorJSON) - if encErr != nil { - // This really shouldn't happen, but if it does let's default to errorBuff - errorString = string(errorBuf) - } else { - errorString = buffer.String() - } + errorString = errorJSON.Message + if errorJSON.Data != nil { + data = *errorJSON.Data } } else { errorString = string(errorBuf) @@ -149,7 +137,7 @@ func extractError(resp *http.Response) error { return unauthorizedRequestError{errorString, apiToken, resp.Request.URL.String()} } - return HTTPError{StatusCode: resp.StatusCode, Status: resp.Status, ErrorString: errorString} + return HTTPError{StatusCode: resp.StatusCode, Status: resp.Status, ErrorString: errorString, Data: data} } // stripTransaction gets a transaction of the form "tx-XXXXXXXX" and truncates the "tx-" part, if it starts with "tx-" diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index a16b11031b..ee306a62b4 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -18,6 +18,7 @@ package v2 import ( "encoding/base64" + "errors" "fmt" "net/http" "strings" @@ -44,7 +45,12 @@ import ( // returnError logs an internal message while returning the encoded response. func returnError(ctx echo.Context, code int, internal error, external string, logger logging.Logger) error { logger.Info(internal) - return ctx.JSON(code, model.ErrorResponse{Message: external}) + var data *map[string]any + var se *basics.SError + if errors.As(internal, &se) { + data = &se.Attrs + } + return ctx.JSON(code, model.ErrorResponse{Message: external, Data: data}) } func badRequest(ctx echo.Context, internal error, external string, log logging.Logger) error { diff --git a/data/basics/serr.go b/data/basics/serr.go new file mode 100644 index 0000000000..229d800d7c --- /dev/null +++ b/data/basics/serr.go @@ -0,0 +1,132 @@ +// Copyright (C) 2019-2024 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 basics + +import ( + "errors" + "strings" + + "golang.org/x/exp/slog" +) + +// SError is a structured error object. It contains a message and an arbitrary +// set of attributes. If the message contains "%A", it will be replaced by the +// attributes (in no guaranteed order), when SError() is called. +// +//msgp:ignore SError +type SError struct { + Msg string + Attrs map[string]any + Wrapped error +} + +// New creates a new structured error object using the supplied message and +// attributes. If the message contains "%A", it will be replaced by the +// attributes when Error() is called. +func New(msg string, pairs ...any) *SError { + attrs := make(map[string]any, len(pairs)/2) + for i := 0; i < len(pairs); i += 2 { + attrs[pairs[i].(string)] = pairs[i+1] + } + return &SError{Msg: msg, Attrs: attrs} +} + +// Error returns either the exact supplied message, or the serialized attributes if +// the supplied message was blank, or substituted for %A. +func (e *SError) Error() string { + if e.Msg == "" { + return e.AttributesAsString() + } + // imperfect because we replace \%A as well + if strings.Contains(e.Msg, "%A") { + return strings.Replace(e.Msg, "%A", e.AttributesAsString(), -1) + } + return e.Msg +} + +// AttributesAsString returns the attributes the same way that slog serializes +// attributes to text in a log message, in no guaranteed order. +func (e *SError) AttributesAsString() string { + var buf strings.Builder + args := make([]any, 0, 2*len(e.Attrs)) + for key, val := range e.Attrs { + args = append(args, key) + args = append(args, val) + } + l := slog.New(slog.NewTextHandler(&buf, nil)) + l.Info("", args...) + return strings.TrimSuffix(strings.SplitN(buf.String(), " ", 4)[3], "\n") +} + +// Annotate adds additional attributes to an existing error, even if the error +// is deep in the error chain. If the supplied error is nil, nil is returned so +// that callers can annotate errors without checking if they are non-nil. If +// the error is not a structured error, it is wrapped in one using its existing +// message and the new attributes. Just like append() for slices, callers should +// re-assign, like this `err = serr.Annotate(err, "x", 100)` +func Annotate(err error, pairs ...any) error { + if err == nil { + return nil + } + var serr *SError + if ok := errors.As(err, &serr); ok { + for i := 0; i < len(pairs); i += 2 { + serr.Attrs[pairs[i].(string)] = pairs[i+1] + } + return err + } + // Since we don't have a structured error, we wrap the existing error in one. + serr = New(err.Error(), pairs...) + serr.Wrapped = err + return serr +} + +// Wrap is used to "demote" an existing error to a field in a new structured +// error. The wrapped error message is added as $field-msg, and if the error is +// structured, the attributes are added under $field-attrs. +func Wrap(err error, msg string, field string, pairs ...any) error { + serr := New(msg, field+"-msg", err.Error()) + for i := 0; i < len(pairs); i += 2 { + serr.Attrs[pairs[i].(string)] = pairs[i+1] + } + serr.Wrapped = err + + var inner *SError + if ok := errors.As(err, &inner); ok { + attributes := make(map[string]any, len(inner.Attrs)) + for key, val := range inner.Attrs { + attributes[key] = val + } + serr.Attrs[field+"-attrs"] = attributes + } + + return serr +} + +// Unwrap returns the inner error, if it exists. +func (e *SError) Unwrap() error { + return e.Wrapped +} + +// Attributes returns the attributes of a structured error, or nil/empty. +func Attributes(err error) map[string]any { + var se *SError + if errors.As(err, &se) { + return se.Attrs + } + return nil +} diff --git a/data/basics/serr_test.go b/data/basics/serr_test.go new file mode 100644 index 0000000000..4df6e20485 --- /dev/null +++ b/data/basics/serr_test.go @@ -0,0 +1,135 @@ +// Copyright (C) 2019-2024 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 basics + +import ( + "errors" + "fmt" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + err := New("test") + assert.Equal(t, "test", err.Error()) +} + +func TestNewWithPairs(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + err := New("test", "a", 7, "b", []byte{3, 4}) + assert.Equal(t, "test", err.Error()) + assert.Equal(t, 7, err.Attrs["a"]) + assert.Equal(t, []byte{3, 4}, err.Attrs["b"]) + + err.Msg = "" + assert.ErrorContains(t, err, `a=7`) + assert.ErrorContains(t, err, `b="\x03\x04"`) + + err.Msg = "check it: %A" + assert.ErrorContains(t, err, ` a=7`) + assert.ErrorContains(t, err, ` b="\x03\x04"`) + assert.Equal(t, `check it: `, err.Error()[:10]) + +} + +func TestAnnotate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + err := New("test", "a", 7, "b", []byte{3, 4}) + assert.Equal(t, 7, err.Attrs["a"]) + assert.Equal(t, nil, err.Attrs["c"]) + Annotate(err, "c", true, "a", false) + assert.Equal(t, true, err.Attrs["c"]) + assert.Equal(t, false, err.Attrs["a"]) +} + +func attribute(err error, name string) any { + var serr *SError + if ok := errors.As(err, &serr); ok { + return serr.Attrs[name] + } + return nil +} + +func TestAnnotateUnstructured(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + err := errors.New("hello") + err = Annotate(err, "c", true, "a", false) + assert.Equal(t, true, attribute(err, "c")) +} + +func TestReannotateEmbedded(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var err error + err = New("test", "a", 7, "b", []byte{3, 4}) + err = fmt.Errorf("embed the above here %w", err) + assert.Equal(t, 7, attribute(err, "a")) + assert.Equal(t, nil, attribute(err, "c")) + Annotate(err, "c", true, "a", false) + assert.Equal(t, true, attribute(err, "c")) + assert.Equal(t, false, attribute(err, "a")) + // "b" is still visible. It would not be is we had _wrapped_ the fmt.Error + assert.Equal(t, []byte{3, 4}, attribute(err, "b")) +} + +func TestWrapBare(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var err error + err = errors.New("inner thingy") + err = Wrap(err, "outer stuff", "xxx") + assert.Equal(t, "inner thingy", attribute(err, "xxx-msg")) + assert.Equal(t, nil, attribute(err, "xxx-attrs")) +} + +func TestWrapStructured(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var err error + err = New("test", "a", 7, "b", []byte{3, 4}) + err = Wrap(err, "outer stuff", "yyy") + assert.Equal(t, "test", attribute(err, "yyy-msg")) + assert.NotNil(t, attribute(err, "yyy-attrs")) + + // these are deeper now, not here + assert.Equal(t, nil, attribute(err, "a")) + assert.Equal(t, nil, attribute(err, "b")) + + // here they are + attrs := attribute(err, "yyy-attrs").(map[string]any) + assert.Equal(t, 7, attrs["a"]) + assert.Equal(t, []byte{3, 4}, attrs["b"]) + + // deeper, with a new attribute + err = Wrap(err, "further out", "again", "name", "jj") + assert.Nil(t, attribute(err, "yyy-msg")) + assert.Equal(t, "outer stuff", attribute(err, "again-msg")) +} diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index fe102086aa..bd56b9b01b 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -66,6 +66,11 @@ var maxAppCallDepth = 8 // maxStackDepth should not change unless controlled by an AVM version change const maxStackDepth = 1000 +// maxTxGroupSize is the same as config.MaxTxGroupSize, but is a constant so +// that we can declare an array of this size. A unit test confirms that they +// match. +const maxTxGroupSize = 16 + // stackValue is the type for the operand stack. // Each stackValue is either a valid []byte value or a uint64 value. // If (.Bytes != nil) the stackValue is a []byte value, otherwise uint64 value. @@ -102,6 +107,17 @@ func (sv stackValue) String() string { return fmt.Sprintf("%d 0x%x", sv.Uint, sv.Uint) } +func (sv stackValue) asAny() any { + if sv.Bytes != nil { + return sv.Bytes + } + return sv.Uint +} + +func (sv stackValue) isEmpty() bool { + return sv.Bytes == nil && sv.Uint == 0 +} + func (sv stackValue) address() (addr basics.Address, err error) { if len(sv.Bytes) != len(addr) { return basics.Address{}, errors.New("not an address") @@ -301,7 +317,7 @@ type EvalParams struct { TxnGroup []transactions.SignedTxnWithAD - pastScratch []*scratchSpace + pastScratch [maxTxGroupSize]*scratchSpace logger logging.Logger @@ -457,7 +473,6 @@ func NewAppEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Cons TxnGroup: copyWithClearAD(txgroup), Proto: proto, Specials: specials, - pastScratch: make([]*scratchSpace, len(txgroup)), minAvmVersion: computeMinAvmVersion(txgroup), FeeCredit: credit, PooledApplicationBudget: pooledApplicationBudget, @@ -525,7 +540,6 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) Proto: caller.Proto, Trace: caller.Trace, TxnGroup: txg, - pastScratch: make([]*scratchSpace, len(txg)), logger: caller.logger, SigLedger: caller.SigLedger, Ledger: caller.Ledger, @@ -968,10 +982,9 @@ var errTooManyArgs = errors.New("LogicSig has too many arguments") // EvalError indicates AVM evaluation failure type EvalError struct { - Err error - details string - groupIndex int - logicsig bool + Err error + details string + logicsig bool } // Error satisfies builtin interface `error` @@ -992,6 +1005,78 @@ func (err EvalError) Unwrap() error { return err.Err } +func (cx *EvalContext) evalError(err error) error { + pc, det := cx.pcDetails() + details := fmt.Sprintf("pc=%d, opcodes=%s", pc, det) + + err = basics.Annotate(err, + "pc", pc, + "group-index", cx.groupIndex, + "eval-states", cx.evalStates()) + if cx.runMode == ModeApp { + details = fmt.Sprintf("app=%d, %s", cx.appID, details) + err = basics.Annotate(err, "app-index", cx.appID) + } + + return EvalError{err, details, cx.runMode == ModeSig} +} + +type evalState struct { + Scratch []any `json:"scratch,omitempty"` + Stack []any `json:"stack,omitempty"` + Logs [][]byte `json:"logs,omitempty"` +} + +func (cx *EvalContext) evalStates() []evalState { + states := make([]evalState, cx.groupIndex+1) + for i := 0; i <= cx.groupIndex; i++ { + var scratch []stackValue + if cx.pastScratch[i] != nil { + scratch = (*cx.pastScratch[i])[:] + } + lastNonZero := -1 + scratchAsAny := make([]any, len(scratch)) + for s, sv := range scratch { + if !sv.isEmpty() { + lastNonZero = s + } + scratchAsAny[s] = sv.asAny() + } + if lastNonZero == -1 { + scratchAsAny = nil + } else { + scratchAsAny = scratchAsAny[:lastNonZero+1] + } + + // Only the current program's stack is still available. So perhaps it + // should be located outside of the evalState, with the PC. + var stack []any + if cx.groupIndex == i { + stack = convertSlice(cx.Stack, func(sv stackValue) any { + return sv.asAny() + }) + } + + states[i] = evalState{ + Scratch: scratchAsAny, + Stack: stack, + Logs: convertSlice(cx.TxnGroup[i].EvalDelta.Logs, func(s string) []byte { return []byte(s) }), + } + } + return states +} + +func convertSlice[X any, Y any](input []X, fn func(X) Y) []Y { + if input == nil { + return nil + } + output := make([]Y, len(input)) + for i := range input { + output[i] = fn(input[i]) + } + return output +} + // EvalContract executes stateful program as the gi'th transaction in params func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (bool, *EvalContext, error) { if params.Ledger == nil { @@ -1013,6 +1098,10 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam txn: ¶ms.TxnGroup[gi], appID: aid, } + // Save scratch for `gload`. We used to copy, but cx.scratch is quite large, + // about 8k, and caused measurable CPU and memory demands. Of course, these + // should never be changed by later transactions. + cx.pastScratch[cx.groupIndex] = &cx.Scratch if cx.Proto.IsolateClearState && cx.txn.Txn.OnCompletion == transactions.ClearStateOC { if cx.PooledApplicationBudget != nil && *cx.PooledApplicationBudget < cx.Proto.MaxAppProgramCost { @@ -1070,7 +1159,13 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam if used > cx.ioBudget { err = fmt.Errorf("box read budget (%d) exceeded", cx.ioBudget) if !cx.Proto.EnableBareBudgetError { - err = EvalError{err, "", gi, false} + // We return an EvalError here because we used to do + // that. It is wrong, and means that there could be a + // ClearState call in an old block that failed on read + // quota, but we allowed to execute anyway. If testnet and + // mainnet have no such transactions, we can remove + // EnableBareBudgetError and this code. + err = EvalError{err, "", false} } return false, nil, err } @@ -1084,20 +1179,13 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam } pass, err := eval(program, &cx) if err != nil { - pc, det := cx.pcDetails() - details := fmt.Sprintf("pc=%d, opcodes=%s", pc, det) - err = EvalError{err, details, gi, false} + err = cx.evalError(err) } if cx.Trace != nil && cx.caller != nil { fmt.Fprintf(cx.Trace, "--- exit %d accept=%t\n", aid, pass) } - // Save scratch for `gload`. We used to copy, but cx.scratch is quite large, - // about 8k, and caused measurable CPU and memory demands. Of course, these - // should never be changed by later transactions. - cx.pastScratch[cx.groupIndex] = &cx.Scratch - return pass, &cx, err } @@ -1123,12 +1211,15 @@ func EvalSignatureFull(gi int, params *EvalParams) (bool, *EvalContext, error) { groupIndex: gi, txn: ¶ms.TxnGroup[gi], } + // Save scratch. `gload*` opcodes are not currently allowed in ModeSig + // (though it seems we could allow them, with access to LogicSig scratch + // values). But error returns and potentially debug code might like to + // return them. + cx.pastScratch[cx.groupIndex] = &cx.Scratch pass, err := eval(cx.txn.Lsig.Logic, &cx) if err != nil { - pc, det := cx.pcDetails() - details := fmt.Sprintf("pc=%d, opcodes=%s", pc, det) - err = EvalError{err, details, gi, true} + err = cx.evalError(err) } return pass, &cx, err @@ -5539,7 +5630,7 @@ func opItxnSubmit(cx *EvalContext) (err error) { } if err != nil { - return err + return basics.Wrap(err, fmt.Sprintf("inner tx %d failed: %s", i, err.Error()), "inner") } // This is mostly a no-op, because Perform does its work "in-place", but diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 21ce259a7e..9a6d0ebc41 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -2006,7 +2006,7 @@ int 1 ledger.NewApp(parentTx.Receiver, parentAppID, basics.AppParams{}) ledger.NewAccount(parentAppID.Address(), 50_000) - parentEd := TestApp(t, parentSource, ep) + parentEd, _ := TestApp(t, parentSource, ep) require.Len(t, parentEd.Logs, 2) require.Len(t, parentEd.InnerTxns, 2) @@ -2334,7 +2334,7 @@ int 1 ledger.NewApp(parentTx.Receiver, parentAppID, basics.AppParams{}) ledger.NewAccount(parentAppID.Address(), 50_000) - parentEd := TestApp(t, parentSource, ep) + parentEd, _ := TestApp(t, parentSource, ep) require.Len(t, parentEd.Logs, 2) require.Len(t, parentEd.InnerTxns, 2) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index fe30d2594a..12d1b8c281 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -422,7 +422,7 @@ func TestBalance(t *testing.T) { } func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, opt protoOpt, ledger *Ledger, - expected ...expect) *EvalParams { + expected ...expect) (*EvalParams, error) { t.Helper() proto := makeTestProto(opt) codes := make([][]byte, len(programs)) @@ -447,11 +447,10 @@ func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, ledger.Reset() ep.Ledger = ledger ep.SigLedger = ledger - testAppsBytes(t, codes, ep, expected...) - return ep + return ep, testAppsBytes(t, codes, ep, expected...) } -func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...expect) { +func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...expect) error { t.Helper() require.LessOrEqual(t, len(programs), len(ep.TxnGroup)) for i := range ep.TxnGroup { @@ -465,20 +464,21 @@ func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ... appID = basics.AppIndex(888) } if len(expected) > 0 && expected[0].l == i { - testAppFull(t, program, i, appID, ep, expected[0].s) - break // Stop after first failure - } else { - testAppFull(t, program, i, appID, ep) + // Stop after first failure + _, err := testAppFull(t, program, i, appID, ep, expected[0].s) + return err } + testAppFull(t, program, i, appID, ep) } else { if len(expected) > 0 && expected[0].l == i { require.Failf(t, "testAppsBytes used incorrectly.", "No error can happen in txn %d. Not an app.", i) } } } + return nil } -func testApp(t *testing.T, program string, ep *EvalParams, problems ...string) transactions.EvalDelta { +func testApp(t *testing.T, program string, ep *EvalParams, problems ...string) (transactions.EvalDelta, error) { t.Helper() if ep == nil { ep = defaultAppParamsWithVersion(LogicVersion) @@ -487,7 +487,7 @@ func testApp(t *testing.T, program string, ep *EvalParams, problems ...string) t return testAppBytes(t, ops.Program, ep, problems...) } -func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) transactions.EvalDelta { +func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) (transactions.EvalDelta, error) { t.Helper() if ep == nil { ep = defaultAppParamsWithVersion(LogicVersion) @@ -505,7 +505,7 @@ func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...stri // ep.reset() is in testAppBytes, not here. This means that ADs in the ep are // not cleared, so repeated use of a single ep is probably not a good idea // unless you are *intending* to see how ep is modified as you go. -func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep *EvalParams, problems ...string) transactions.EvalDelta { +func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep *EvalParams, problems ...string) (transactions.EvalDelta, error) { t.Helper() var checkProblem string @@ -545,7 +545,7 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep * if evalProblem == "" { require.NoError(t, err, "Eval\n%sExpected: PASS", ep.Trace) require.True(t, pass, "Eval\n%sExpected: PASS", ep.Trace) - return delta + return delta, nil } // There is an evalProblem to check. REJECT is special and only means that @@ -555,7 +555,7 @@ func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep * } else { require.ErrorContains(t, err, evalProblem, "Wrong error in EvalContract %v", ep.Trace) } - return delta + return delta, err } // testLogicRange allows for running tests against a range of avm @@ -819,6 +819,122 @@ int 0 testApp(t, text, now) } +// TestAppErrorDetails confirms that the error returned from app failures +// has the right structured information. +func TestAppErrorDetails(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + badsource := notrack(` +byte 0x04040004; log // log +int 5; store 10 // store an int +byte 0x01020300; store 15 // store a bytes + +int 100; byte 0x0201; == // types mismatch so this will fail +`) + _, err := testApp(t, badsource, nil, "cannot compare") + attrs := basics.Attributes(err) + zeros := [256]int{} + scratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + scratch[10] = uint64(5) + scratch[15] = []byte{0x01, 0x02, 0x03, 0x00} + require.Equal(t, map[string]any{ + "pc": 26, + "group-index": 0, + "app-index": basics.AppIndex(888), + "eval-states": []evalState{ + { + Logs: [][]byte{{0x04, 0x04, 0x00, 0x04}}, + Stack: []any{uint64(100), []byte{02, 01}}, + Scratch: scratch[:16], + }, + }, + }, attrs) + + goodsource := ` +byte 0x04040104; log // log +byte 0x04040204; log // log + +int 4; store 2 // store an int +byte "jj"; store 3 // store a bytes +int 1 +` + gscratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + gscratch[2] = uint64(4) + gscratch[3] = []byte("jj") + + _, err = testApps(t, []string{goodsource, badsource}, nil, nil, nil, exp(1, "cannot compare")) + attrs = basics.Attributes(err) + require.Equal(t, map[string]any{ + "pc": 26, + "group-index": 1, + "app-index": basics.AppIndex(888), + "eval-states": []evalState{ + { + Logs: [][]byte{ + {0x04, 0x04, 0x01, 0x04}, + {0x04, 0x04, 0x02, 0x04}, + }, + Scratch: gscratch[:4], + }, + { + Logs: [][]byte{{0x04, 0x04, 0x00, 0x04}}, + Stack: []any{uint64(100), []byte{02, 01}}, + Scratch: scratch[:16], + }, + }, + }, attrs) + + _, _, ledger := makeSampleEnv() + ledger.NewAccount(appAddr(888), 100_000) + bad := testProg(t, badsource, 5) + innerFailSource := ` +int 777 +itxn_begin +int appl; itxn_field TypeEnum +byte 0x` + hex.EncodeToString(bad.Program) + ` // run the bad program by trying to create it +itxn_field ApprovalProgram + +byte 0x05 +itxn_field ClearStateProgram + +itxn_submit +` + _, err = testApps(t, []string{goodsource, innerFailSource}, nil, nil, ledger, exp(1, "inner tx 0 failed")) + attrs = basics.Attributes(err) + require.Equal(t, map[string]any{ + "pc": 45, + "group-index": 1, + "app-index": basics.AppIndex(888), + "eval-states": []evalState{ + { + Logs: [][]byte{ + {0x04, 0x04, 0x01, 0x04}, + {0x04, 0x04, 0x02, 0x04}, + }, + Scratch: gscratch[:4], + }, + { + Stack: []any{uint64(777)}, + }, + }, + "inner-msg": "logic eval error: cannot compare (uint64 to []byte). Details: app=5000, pc=26, opcodes=pushint 100; pushbytes 0x0201 // 0x0201; ==", + "inner-attrs": map[string]any{ + "pc": 26, + "group-index": 0, + "app-index": basics.AppIndex(firstTestID), + "eval-states": []evalState{ + { + Logs: [][]byte{{0x04, 0x04, 0x00, 0x04}}, + Stack: []any{uint64(100), []byte{02, 01}}, + Scratch: scratch[:16], + }, + }, + }, + }, attrs) + +} + func TestAppReadGlobalState(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1493,7 +1609,7 @@ func TestGlobalNonDelete(t *testing.T) { testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) { source := `byte "none"; app_global_del; int 1` ledger.NewApp(txn.Sender, 888, makeApp(0, 0, 1, 0)) - delta := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) }) @@ -1509,7 +1625,7 @@ func TestLocalNonDelete(t *testing.T) { ledger.NewAccount(txn.Sender, 100000) ledger.NewApp(txn.Sender, 888, makeApp(0, 0, 1, 0)) ledger.NewLocals(txn.Sender, 888) - delta := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) }) @@ -1660,7 +1776,7 @@ int 0x77 if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Len(t, delta.LocalDeltas, 1) @@ -1698,7 +1814,7 @@ int 0x77 algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} ledger.NewLocal(txn.Sender, 100, "ALGO", algoValue) - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) @@ -1730,7 +1846,7 @@ exist2: if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) @@ -1748,7 +1864,7 @@ int 1 if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Len(t, delta.LocalDeltas, 1) require.Len(t, delta.LocalDeltas[0], 1) @@ -1778,7 +1894,7 @@ int 0x78 if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Len(t, delta.LocalDeltas, 1) require.Len(t, delta.LocalDeltas[0], 1) @@ -1806,7 +1922,7 @@ app_local_put if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Len(t, delta.LocalDeltas, 1) require.Len(t, delta.LocalDeltas[0], 1) @@ -1844,7 +1960,7 @@ int 1 source = strings.ReplaceAll(source, "txn Sender", "int 0") source = strings.ReplaceAll(source, "txn Accounts 1", "int 1") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Len(t, delta.LocalDeltas, 2) require.Len(t, delta.LocalDeltas[0], 2) @@ -1934,7 +2050,7 @@ int 0x77 ledger.Reset() - delta := testAppBytes(t, ops.Program, ep) + delta, _ := testAppBytes(t, ops.Program, ep) require.Empty(t, delta.LocalDeltas) } }) @@ -2023,7 +2139,7 @@ int 0x77 } source = strings.ReplaceAll(source, "THISAPP", "int 100") } - delta := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 2) require.Empty(t, delta.LocalDeltas) @@ -2052,7 +2168,7 @@ int 0x77 algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) @@ -2076,7 +2192,7 @@ int 0x77 ledger.NoGlobal(100, "ALGOA") ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) @@ -2116,7 +2232,7 @@ byte "ALGO" ledger.NoGlobal(100, "ALGOA") ledger.NewGlobal(100, "ALGO", algoValue) - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 2) require.Empty(t, delta.LocalDeltas) @@ -2172,7 +2288,7 @@ byte "myval" ledger.NewAccount(txn.Sender, 1) ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep, "no app 101") + delta, _ := testApp(t, source, ep, "no app 101") require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) @@ -2181,7 +2297,7 @@ byte "myval" algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} ledger.NewGlobal(101, "mykey", algoValue) - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) }) @@ -2214,7 +2330,7 @@ int 7 ledger.NewAccount(txn.Sender, 1) ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Empty(t, delta.LocalDeltas) }) } @@ -2256,7 +2372,7 @@ int 1 txn.ApplicationID = 100 ledger.NewApp(txn.Sender, 100, basics.AppParams{}) - delta := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 2) require.Empty(t, delta.LocalDeltas) @@ -2287,7 +2403,7 @@ app_global_get_ex source = strings.ReplaceAll(source, "THISAPP", "int 100") } txn.ForeignApps = []basics.AppIndex{txn.ApplicationID} - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 1) vd := delta.GlobalDelta["ALGO"] require.Equal(t, basics.DeleteAction, vd.Action) @@ -2312,7 +2428,7 @@ byte "ALGOA" int 0x78 app_global_put ` - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 1) vd = delta.GlobalDelta["ALGOA"] require.Equal(t, basics.SetUintAction, vd.Action) @@ -2334,7 +2450,7 @@ int 0x78 app_global_put int 1 ` - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 1) vd = delta.GlobalDelta["ALGO"] require.Equal(t, basics.SetUintAction, vd.Action) @@ -2357,7 +2473,7 @@ byte "ALGO" app_global_del int 1 ` - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 1) vd = delta.GlobalDelta["ALGO"] require.Equal(t, basics.DeleteAction, vd.Action) @@ -2380,7 +2496,7 @@ byte "ALGOA" app_global_del int 1 ` - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 1) require.Len(t, delta.LocalDeltas, 0) }) @@ -2433,7 +2549,7 @@ int 1 ep.Trace = &strings.Builder{} - delta := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 2, len(delta.LocalDeltas)) ledger.Reset() @@ -2442,7 +2558,7 @@ int 1 // test that app_local_put and _app_local_del can use byte addresses withBytes := strings.ReplaceAll(source, "int 0 // sender", "txn Sender") withBytes = strings.ReplaceAll(withBytes, "int 1 // other", "txn Accounts 1") - delta := testApp(t, withBytes, ep) + delta, _ := testApp(t, withBytes, ep) // But won't even compile in old teal testProg(t, withBytes, directRefEnabledVersion-1, exp(4, "app_local_put arg 0 wanted..."), exp(11, "app_local_del arg 0 wanted...")) @@ -2473,7 +2589,7 @@ app_local_get_ex if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 1, len(delta.LocalDeltas)) vd := delta.LocalDeltas[0]["ALGO"] @@ -2504,7 +2620,7 @@ app_local_put if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 1, len(delta.LocalDeltas)) vd = delta.LocalDeltas[0]["ALGOA"] @@ -2531,7 +2647,7 @@ int 1 if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 1, len(delta.LocalDeltas)) vd = delta.LocalDeltas[0]["ALGO"] @@ -2561,7 +2677,7 @@ int 1 if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 1, len(delta.LocalDeltas)) vd = delta.LocalDeltas[0]["ALGO"] @@ -2591,7 +2707,7 @@ int 1 if ep.Proto.LogicSigVersion < directRefEnabledVersion { source = strings.ReplaceAll(source, "txn Sender", "int 0") } - delta = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 1, len(delta.LocalDeltas)) require.Equal(t, 1, len(delta.LocalDeltas[0])) @@ -3328,7 +3444,7 @@ func TestLog(t *testing.T) { //track expected number of logs in cx.EvalDelta.Logs for i, s := range testCases { - delta := testApp(t, s.source, ep) + delta, _ := testApp(t, s.source, ep) require.Len(t, delta.Logs, s.loglen) if i == len(testCases)-1 { require.Equal(t, strings.Repeat("a", maxLogSize), delta.Logs[0]) @@ -3653,7 +3769,7 @@ int 43 app_local_put int 1 ` - ed := testApp(t, source, ep) + delta, _ := testApp(t, source, ep) require.Len(t, tx.Accounts, 1) // Sender + 1 tx.Accounts means LocalDelta index should be 2 require.Equal(t, map[uint64]basics.StateDelta{ 1: { @@ -3668,8 +3784,8 @@ int 1 Uint: 42, }, }, - }, ed.LocalDeltas) - require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, ed.SharedAccts) + }, delta.LocalDeltas) + require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, delta.SharedAccts) /* Confirm it worked. */ source = ` @@ -3695,7 +3811,7 @@ int 7 app_local_put int 1 ` - ed = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, tx.Accounts, 1) // Sender + 1 tx.Accounts means LocalDelta index should be 2 require.Equal(t, map[uint64]basics.StateDelta{ 1: { @@ -3709,8 +3825,8 @@ int 1 Action: basics.DeleteAction, }, }, - }, ed.LocalDeltas) - require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, ed.SharedAccts) + }, delta.LocalDeltas) + require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, delta.SharedAccts) // Now, repeat the "put" test with multiple keys, to ensure only one // address is added to SharedAccts and we'll modify the Sender too, to @@ -3734,7 +3850,7 @@ int 21 app_local_put int 1 ` - ed = testApp(t, source, ep) + delta, _ = testApp(t, source, ep) require.Len(t, tx.Accounts, 1) // Sender + 1 tx.Accounts means LocalDelta index should be 2 require.Equal(t, map[uint64]basics.StateDelta{ 0: { @@ -3753,9 +3869,9 @@ int 1 Uint: 21, }, }, - }, ed.LocalDeltas) + }, delta.LocalDeltas) - require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, ed.SharedAccts) + require.Equal(t, []basics.Address{tx.ApplicationID.Address()}, delta.SharedAccts) }) } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 0eceac9a33..b7c55faf87 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -217,7 +217,7 @@ func (ep *EvalParams) reset() { inners := ep.Proto.MaxTxGroupSize * ep.Proto.MaxInnerTransactions ep.pooledAllowedInners = &inners } - ep.pastScratch = make([]*scratchSpace, len(ep.TxnGroup)) + ep.pastScratch = [maxTxGroupSize]*scratchSpace{} for i := range ep.TxnGroup { ep.TxnGroup[i].ApplyData = transactions.ApplyData{} } @@ -2182,7 +2182,7 @@ func testLogicBytes(t *testing.T, program []byte, ep *EvalParams, problems ...st } // testLogicFull is the lowest-level so it does not create an ep or reset it. -func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems ...string) { +func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems ...string) error { t.Helper() var checkProblem string @@ -2217,7 +2217,7 @@ func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problem if evalProblem == "" { require.NoError(t, err, "Eval\n%sExpected: PASS", ep.Trace) assert.True(t, pass, "Eval\n%sExpected: PASS", ep.Trace) - return + return nil } // There is an evalProblem to check. REJECT is special and only means that @@ -2227,9 +2227,10 @@ func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problem } else { require.ErrorContains(t, err, evalProblem, "Wrong error in EvalSignature %v", ep.Trace) } + return err } -func testLogics(t *testing.T, programs []string, txgroup []transactions.SignedTxn, opt protoOpt, expected ...expect) *EvalParams { +func testLogics(t *testing.T, programs []string, txgroup []transactions.SignedTxn, opt protoOpt, expected ...expect) error { t.Helper() proto := makeTestProto(opt) @@ -2248,10 +2249,14 @@ func testLogics(t *testing.T, programs []string, txgroup []transactions.SignedTx ep := NewSigEvalParams(txgroup, proto, &NoHeaderLedger{}) for i, program := range programs { if program != "" { + if len(expected) > 0 && expected[0].l == i { + // Stop after first failure + return testLogicFull(t, txgroup[i].Lsig.Logic, i, ep, expected[0].s) + } testLogicFull(t, txgroup[i].Lsig.Logic, i, ep) } } - return ep + return nil } func TestTxna(t *testing.T) { @@ -2571,12 +2576,11 @@ substring3 len`, 2) // fails at runtime - err := testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 int 4 int 0xFFFFFFFFFFFFFFFE substring3 -len`, 2) - require.Contains(t, err.Error(), "substring range beyond length of string") +len`, 2, "substring range beyond length of string") } func TestSubstringRange(t *testing.T) { @@ -2629,44 +2633,37 @@ func TestExtractFlop(t *testing.T) { len`, 5, exp(4, "extract3 expects 0 immediate arguments")) // fails at runtime - err := testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 extract 1 8 - len`, 5) - require.Contains(t, err.Error(), "extraction end 9") + len`, 5, "extraction end 9") - err = testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 extract 9 0 - len`, 5) - require.Contains(t, err.Error(), "extraction start 9") + len`, 5, "extraction start 9") - err = testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 int 4 int 0xFFFFFFFFFFFFFFFE extract3 - len`, 5) - require.Contains(t, err.Error(), "extraction end exceeds uint64") + len`, 5, "extraction end exceeds uint64") - err = testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 int 100 int 2 extract3 - len`, 5) - require.Contains(t, err.Error(), "extraction start 100") + len`, 5, "extraction start 100") - err = testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 int 55 - extract_uint16`, 5) - require.Contains(t, err.Error(), "extraction start 55") + extract_uint16`, 5, "extraction start 55") - err = testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 int 9 - extract_uint32`, 5) - require.Contains(t, err.Error(), "extraction start 9") + extract_uint32`, 5, "extraction start 9") - err = testPanics(t, `byte 0xf000000000000000 + testPanics(t, `byte 0xf000000000000000 int 1 - extract_uint64`, 5) - require.Contains(t, err.Error(), "extraction end 9") + extract_uint64`, 5, "extraction end 9") } func TestReplace(t *testing.T) { @@ -2752,8 +2749,8 @@ loads func TestLoadStore2(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + progText := `int 2 int 3 byte 0xaa @@ -2768,6 +2765,61 @@ int 5 testAccepts(t, progText, 1) } +// TestLogicErrorDetails confirms that the error returned from logicsig failures +// has the right structured information. +func TestLogicErrorDetails(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + badsource := notrack(` +int 5; store 10 // store an int +byte 0x01020300; store 15 // store a bytes + +int 100; byte 0x0201; == // types mismatch so this will fail +`) + err := testPanics(t, badsource, 1, "cannot compare") + attrs := basics.Attributes(err) + zeros := [256]int{} + scratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + scratch[10] = uint64(5) + scratch[15] = []byte{0x01, 0x02, 0x03, 0x00} + require.Equal(t, map[string]any{ + "pc": 19, + "group-index": 0, + "eval-states": []evalState{ + { + Stack: []any{uint64(100), []byte{02, 01}}, + Scratch: scratch[:16], + }, + }, + }, attrs) + + goodsource := ` +int 4; store 2 // store an int +byte "jj"; store 3 // store a bytes +int 1 +` + gscratch := convertSlice(zeros[:], func(i int) any { return uint64(i) }) + gscratch[2] = uint64(4) + gscratch[3] = []byte("jj") + + err = testLogics(t, []string{goodsource, badsource}, nil, nil, exp(1, "cannot compare")) + attrs = basics.Attributes(err) + require.Equal(t, map[string]any{ + "pc": 19, + "group-index": 1, + "eval-states": []evalState{ + { + Scratch: gscratch[:4], + }, + { + Stack: []any{uint64(100), []byte{02, 01}}, + Scratch: scratch[:16], + }, + }, + }, attrs) +} + func TestGload(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -4563,7 +4615,11 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval require.True(t, ok) isNotPanic(t, err) // Never want a Go level panic. if err != nil { - // Use wisely. This could probably return any of the concurrent runs' errors. + // Use `outer` wisely. It could return any of the concurrent runs' errors. + var se *basics.SError + require.ErrorAs(t, err, &se) + var ee EvalError + require.ErrorAs(t, err, &ee) outer = err } }) @@ -5278,9 +5334,7 @@ By Herman Melville`, "", if LogicVersion < fidoVersion { testProg(t, source, AssemblerMaxVersion, exp(0, "unknown opcode...")) } else { - err := testPanics(t, source, fidoVersion) - require.Error(t, err) - require.Contains(t, err.Error(), tc.error) + testPanics(t, source, fidoVersion, tc.error) } } } @@ -6122,9 +6176,17 @@ int 1 func TestNoHeaderLedger(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() nhl := NoHeaderLedger{} _, err := nhl.BlockHdr(1) require.Error(t, err) require.Equal(t, err, fmt.Errorf("no block header access")) } + +func TestMaxTxGroup(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + require.Equal(t, config.MaxTxGroupSize, maxTxGroupSize) +} diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index 3ba93a87e6..ed250acd81 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -60,7 +60,7 @@ var WithPanicOpcode = withPanicOpcode // TryApps exports "testApps" while accepting a simple uint64. Annoying, we // can't export call this "TestApps" because it looks like a Test function with // the wrong signature. But we can get that effect with the alias below. -func TryApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, ver uint64, ledger *Ledger, expected ...expect) *EvalParams { +func TryApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, ver uint64, ledger *Ledger, expected ...expect) (*EvalParams, error) { return testApps(t, programs, txgroup, protoVer(ver), ledger, expected...) } diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 6c2d62c7f0..0254a44678 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -625,7 +625,7 @@ func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) err tbr = newBalanceRecord(to, 0) } if fbr.balance < amount { - return fmt.Errorf("insufficient balance") + return fmt.Errorf("insufficient balance in %v. %d < %d", from, fbr.balance, amount) } fbr.balance -= amount tbr.balance += amount diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index 42ceee6d45..6458e081a1 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -141,7 +141,7 @@ func TestAppSharing(t *testing.T) { TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, Exp(1, "account "+appl0.Sender.String()+" is not opted into 901")) ledger.NewLocals(appl0.Sender, 901) // opt in - ep := TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) + ep, _ := TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger) require.Len(t, ep.TxnGroup, 2) ed := ep.TxnGroup[1].ApplyData.EvalDelta require.Equal(t, map[uint64]basics.StateDelta{ diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 100ea8b7f1..41482e72e2 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -6813,7 +6813,7 @@ int 1`, input: simulation.Request{ TxnGroups: [][]transactions.SignedTxn{txgroup}, }, - expectedError: "logic eval error: this transaction should be issued by the manager", + expectedError: "this transaction should be issued by the manager", expected: simulation.Result{ Version: simulation.ResultLatestVersion, LastRound: env.TxnInfo.LatestRound(), diff --git a/test/e2e-go/cli/goal/expect/goalAppAccountAddressTest.exp b/test/e2e-go/cli/goal/expect/goalAppAccountAddressTest.exp old mode 100644 new mode 100755 diff --git a/test/e2e-go/cli/goal/expect/statefulTealCreateAppTest.exp b/test/e2e-go/cli/goal/expect/statefulTealCreateAppTest.exp old mode 100755 new mode 100644 diff --git a/test/e2e-go/cli/goal/expect/tealConsensusTest.exp b/test/e2e-go/cli/goal/expect/tealConsensusTest.exp old mode 100755 new mode 100644 diff --git a/test/e2e-go/restAPI/other/appsRestAPI_test.go b/test/e2e-go/restAPI/other/appsRestAPI_test.go index 51d757af15..c4a4986ed9 100644 --- a/test/e2e-go/restAPI/other/appsRestAPI_test.go +++ b/test/e2e-go/restAPI/other/appsRestAPI_test.go @@ -31,7 +31,6 @@ import ( "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" @@ -319,15 +318,12 @@ end: 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.Equal("Result limit exceeded", e.ErrorString) + a.EqualValues(100000, e.Data["max-api-box-per-application"]) + a.EqualValues(requestedMax, e.Data["max"]) + a.EqualValues(expectedCount, e.Data["total-boxes"]) - a.Len(*er.Data, 3, fmt.Sprintf("error response (%v) contains unverified fields. Extend test for new fields.", *er.Data)) + a.Len(e.Data, 3, fmt.Sprintf("error response (%v) contains unverified fields. Extend test for new fields.", e.Data)) } // `assertBoxCount` sanity checks that the REST API respects `expectedCount` through different queries against app ID = `createdAppID`. diff --git a/test/scripts/e2e_subs/avm-failure-info.py b/test/scripts/e2e_subs/avm-failure-info.py new file mode 100755 index 0000000000..5f2adfab46 --- /dev/null +++ b/test/scripts/e2e_subs/avm-failure-info.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +import os +import json +import subprocess +import sys +from goal import Goal + +from datetime import datetime + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} start {stamp}") + +goal = Goal(sys.argv[1], autosend=True) + +joe = goal.new_account() + +txinfo, err = goal.pay(goal.account, joe, amt=500_000) +assert not err, err + +teal = """ +#pragma version 6 + byte 0x1a00011afb // be sure to test non-ascii logs + byte 0x2a00012afc // be sure to test non-ascii logs + log + log + int 42 + byte 0x4a01004afa // be sure to test non-ascii stack + int 7 + store 10 + byte 0x4a00014afe // be sure to test non-ascii scratch + store 12 + int 1 + int 2 + - // Fail! +end: + int 1 +""" + +tx = goal.app_create(joe, goal.assemble(teal), send=False) +command = goal.curl_command(tx) +response = subprocess.check_output(command, shell=True) +j = json.loads(response) +print(j) +assert j['data']['pc'] == 45 +assert j['data']['group-index'] == 0 +assert j['data']['app-index'] > 1000 +assert j['data']['eval-states'][0]['scratch'][10] == 7 +assert j['data']['eval-states'][0]['scratch'][12] == 'SgABSv4=' +assert j['data']['eval-states'][0]['stack'] == [42, 'SgEASvo=', 1, 2] +assert j['data']['eval-states'][0]['logs'] == ['KgABKvw=', 'GgABGvs='] + +# Test some omit-empty behavior. That `scratch` simply doesn't appear, +# and that `logs` does appear, even if the only log entry is an empty +# message. +teal = """ +#pragma version 6 + byte 0x; log // Log (only) an empty msg + int 1 + int 2 + - // Fail! +end: + int 1 +""" + +tx = goal.app_create(joe, goal.assemble(teal), send=False) +command = goal.curl_command(tx) +response = subprocess.check_output(command, shell=True) +j = json.loads(response) +print(j) + +assert j['data']['group-index'] == 0 +assert 'scratch' not in j['data']['eval-states'][0] +assert j['data']['eval-states'][0]['logs'] == [''] +assert j['data']['pc'] == 10 + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/goal/goal.py b/test/scripts/e2e_subs/goal/goal.py index 812afe8976..5d0dd7db0f 100755 --- a/test/scripts/e2e_subs/goal/goal.py +++ b/test/scripts/e2e_subs/goal/goal.py @@ -158,6 +158,19 @@ def send(self, tx, confirm=True): except algosdk.error.AlgodHTTPError as e: return (None, e) + def send_details(self, tx): + stx = self.sign(tx) + headers = {"Content-Type": "application/x-binary", + "X-Algo-API-Token": self.algod.algod_token, + } + url = self.algod.algod_address + "/v2/transactions" + return (url, headers, enc.msgpack_encode(stx)) + + def curl_command(self, tx): + (url, headers, b64data) = self.send_details(tx) + H = " ".join(['-H "' + k + ':' + v + '"' for k,v in headers.items()]) + return f"echo {b64data} | base64 -d | curl -s {url} {H} --data-binary @-" + def send_group(self, txns, confirm=True): # Need unsigned transactions to calculate the group This pulls # out the unsigned tx if tx is sigged, logicsigged or From d8dfaadadf73cfd1a10198dd9660afb7276bce09 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 19 Jan 2024 11:08:18 -0500 Subject: [PATCH 067/218] AVM: Add a lot of type annotations to opcodes (#5902) --- data/transactions/logic/README.md | 3 - data/transactions/logic/TEAL_opcodes_v1.md | 2 +- data/transactions/logic/TEAL_opcodes_v10.md | 32 ++++---- data/transactions/logic/TEAL_opcodes_v2.md | 18 ++--- data/transactions/logic/TEAL_opcodes_v3.md | 18 ++--- data/transactions/logic/TEAL_opcodes_v4.md | 20 ++--- data/transactions/logic/TEAL_opcodes_v5.md | 26 +++---- data/transactions/logic/TEAL_opcodes_v6.md | 28 +++---- data/transactions/logic/TEAL_opcodes_v7.md | 32 ++++---- data/transactions/logic/TEAL_opcodes_v8.md | 32 ++++---- data/transactions/logic/TEAL_opcodes_v9.md | 32 ++++---- data/transactions/logic/crypto_test.go | 25 +++---- data/transactions/logic/eval.go | 30 ++++++-- data/transactions/logic/evalStateful_test.go | 7 +- data/transactions/logic/eval_test.go | 4 +- data/transactions/logic/langspec_v1.json | 29 +------- data/transactions/logic/langspec_v10.json | 77 +++++++------------- data/transactions/logic/langspec_v2.json | 45 +++--------- data/transactions/logic/langspec_v3.json | 45 +++--------- data/transactions/logic/langspec_v4.json | 51 +++---------- data/transactions/logic/langspec_v5.json | 69 ++++++------------ data/transactions/logic/langspec_v6.json | 73 ++++++------------- data/transactions/logic/langspec_v7.json | 77 +++++++------------- data/transactions/logic/langspec_v8.json | 77 +++++++------------- data/transactions/logic/langspec_v9.json | 77 +++++++------------- data/transactions/logic/opcodes.go | 70 +++++++++--------- 26 files changed, 369 insertions(+), 630 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index a38ba1462e..e1c5cfe218 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -52,9 +52,6 @@ assembly time to do type checking and to provide more informative error messages | Name | Bound | AVM Type | | ---- | ---- | -------- | -| [32]byte | len(x) == 32 | []byte | -| [64]byte | len(x) == 64 | []byte | -| [80]byte | len(x) == 80 | []byte | | []byte | len(x) <= 4096 | []byte | | address | len(x) == 32 | []byte | | any | | any | diff --git a/data/transactions/logic/TEAL_opcodes_v1.md b/data/transactions/logic/TEAL_opcodes_v1.md index f255c4bec5..35bcca9255 100644 --- a/data/transactions/logic/TEAL_opcodes_v1.md +++ b/data/transactions/logic/TEAL_opcodes_v1.md @@ -133,7 +133,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi diff --git a/data/transactions/logic/TEAL_opcodes_v10.md b/data/transactions/logic/TEAL_opcodes_v10.md index 83c6e9dfeb..b90801521b 100644 --- a/data/transactions/logic/TEAL_opcodes_v10.md +++ b/data/transactions/logic/TEAL_opcodes_v10.md @@ -43,7 +43,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} -- Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool +- Stack: ..., A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: Secp256k1=1700; Secp256r1=2500 - Availability: v5 @@ -64,7 +64,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} -- Stack: ..., A: []byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [33]byte → ..., X: [32]byte, Y: [32]byte - decompress pubkey A into components X, Y - **Cost**: Secp256k1=650; Secp256r1=2400 - Availability: v5 @@ -75,7 +75,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top - Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} -- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: [32]byte, Y: [32]byte - for (data A, recovery id B, signature C, D) recover a public key - **Cost**: 2000 - Availability: v5 @@ -175,7 +175,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -896,7 +896,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A, B: []byte → ..., any +- Stack: ..., A, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -906,7 +906,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -916,7 +916,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -926,7 +926,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -936,7 +936,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A, B: []byte, C → ... +- Stack: ..., A, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -946,7 +946,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -954,7 +954,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A, B: []byte → ... +- Stack: ..., A, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -966,7 +966,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application @@ -1254,7 +1254,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## bsqrt - Bytecode: 0x96 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: bigint → ..., bigint - The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers - **Cost**: 40 - Availability: v6 @@ -1271,7 +1271,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## sha3_256 - Bytecode: 0x98 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: []byte → ..., [32]byte - SHA3_256 hash of value A, yields [32]byte - **Cost**: 130 - Availability: v7 @@ -1353,7 +1353,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b% - Bytecode: 0xaa -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Stack: ..., A: bigint, B: bigint → ..., bigint - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 @@ -1608,7 +1608,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo - Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} -- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool +- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: [64]byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. - **Cost**: 5700 - Availability: v7 diff --git a/data/transactions/logic/TEAL_opcodes_v2.md b/data/transactions/logic/TEAL_opcodes_v2.md index 0f0fa92488..84d21cb744 100644 --- a/data/transactions/logic/TEAL_opcodes_v2.md +++ b/data/transactions/logic/TEAL_opcodes_v2.md @@ -133,7 +133,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -540,7 +540,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A: uint64, B: []byte → ..., any +- Stack: ..., A: uint64, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -550,7 +550,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A: uint64, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -560,7 +560,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -570,7 +570,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -580,7 +580,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A: uint64, B: []byte, C → ... +- Stack: ..., A: uint64, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -590,7 +590,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -598,7 +598,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A: uint64, B: []byte → ... +- Stack: ..., A: uint64, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -610,7 +610,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application diff --git a/data/transactions/logic/TEAL_opcodes_v3.md b/data/transactions/logic/TEAL_opcodes_v3.md index b8e2c7e963..b9b21b141f 100644 --- a/data/transactions/logic/TEAL_opcodes_v3.md +++ b/data/transactions/logic/TEAL_opcodes_v3.md @@ -133,7 +133,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -628,7 +628,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A: uint64, B: []byte → ..., any +- Stack: ..., A: uint64, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -638,7 +638,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A: uint64, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -648,7 +648,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -658,7 +658,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -668,7 +668,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A: uint64, B: []byte, C → ... +- Stack: ..., A: uint64, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -678,7 +678,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -686,7 +686,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A: uint64, B: []byte → ... +- Stack: ..., A: uint64, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -698,7 +698,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application diff --git a/data/transactions/logic/TEAL_opcodes_v4.md b/data/transactions/logic/TEAL_opcodes_v4.md index 66ebc2cc21..2fb554bed1 100644 --- a/data/transactions/logic/TEAL_opcodes_v4.md +++ b/data/transactions/logic/TEAL_opcodes_v4.md @@ -133,7 +133,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -682,7 +682,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A, B: []byte → ..., any +- Stack: ..., A, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -692,7 +692,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -702,7 +702,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -712,7 +712,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -722,7 +722,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A, B: []byte, C → ... +- Stack: ..., A, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -732,7 +732,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -740,7 +740,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A, B: []byte → ... +- Stack: ..., A, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -752,7 +752,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application @@ -984,7 +984,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## b% - Bytecode: 0xaa -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Stack: ..., A: bigint, B: bigint → ..., bigint - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 diff --git a/data/transactions/logic/TEAL_opcodes_v5.md b/data/transactions/logic/TEAL_opcodes_v5.md index 46c79662a0..92f52f59a2 100644 --- a/data/transactions/logic/TEAL_opcodes_v5.md +++ b/data/transactions/logic/TEAL_opcodes_v5.md @@ -43,7 +43,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} -- Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool +- Stack: ..., A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: Secp256k1=1700 - Availability: v5 @@ -63,7 +63,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} -- Stack: ..., A: []byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [33]byte → ..., X: [32]byte, Y: [32]byte - decompress pubkey A into components X, Y - **Cost**: Secp256k1=650 - Availability: v5 @@ -74,7 +74,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top - Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} -- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: [32]byte, Y: [32]byte - for (data A, recovery id B, signature C, D) recover a public key - **Cost**: 2000 - Availability: v5 @@ -174,7 +174,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -796,7 +796,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A, B: []byte → ..., any +- Stack: ..., A, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -806,7 +806,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -816,7 +816,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -826,7 +826,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -836,7 +836,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A, B: []byte, C → ... +- Stack: ..., A, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -846,7 +846,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -854,7 +854,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A, B: []byte → ... +- Stack: ..., A, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -866,7 +866,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application @@ -1127,7 +1127,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## b% - Bytecode: 0xaa -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Stack: ..., A: bigint, B: bigint → ..., bigint - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 diff --git a/data/transactions/logic/TEAL_opcodes_v6.md b/data/transactions/logic/TEAL_opcodes_v6.md index 56d79cd072..bacca46d45 100644 --- a/data/transactions/logic/TEAL_opcodes_v6.md +++ b/data/transactions/logic/TEAL_opcodes_v6.md @@ -43,7 +43,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} -- Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool +- Stack: ..., A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: Secp256k1=1700 - Availability: v5 @@ -63,7 +63,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} -- Stack: ..., A: []byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [33]byte → ..., X: [32]byte, Y: [32]byte - decompress pubkey A into components X, Y - **Cost**: Secp256k1=650 - Availability: v5 @@ -74,7 +74,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top - Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} -- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: [32]byte, Y: [32]byte - for (data A, recovery id B, signature C, D) recover a public key - **Cost**: 2000 - Availability: v5 @@ -174,7 +174,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -801,7 +801,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A, B: []byte → ..., any +- Stack: ..., A, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -811,7 +811,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -821,7 +821,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -831,7 +831,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -841,7 +841,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A, B: []byte, C → ... +- Stack: ..., A, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -851,7 +851,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -859,7 +859,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A, B: []byte → ... +- Stack: ..., A, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -871,7 +871,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application @@ -1078,7 +1078,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## bsqrt - Bytecode: 0x96 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: bigint → ..., bigint - The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers - **Cost**: 40 - Availability: v6 @@ -1169,7 +1169,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b% - Bytecode: 0xaa -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Stack: ..., A: bigint, B: bigint → ..., bigint - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 diff --git a/data/transactions/logic/TEAL_opcodes_v7.md b/data/transactions/logic/TEAL_opcodes_v7.md index 3536f35f8a..3a0c678b00 100644 --- a/data/transactions/logic/TEAL_opcodes_v7.md +++ b/data/transactions/logic/TEAL_opcodes_v7.md @@ -43,7 +43,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} -- Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool +- Stack: ..., A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: Secp256k1=1700; Secp256r1=2500 - Availability: v5 @@ -64,7 +64,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} -- Stack: ..., A: []byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [33]byte → ..., X: [32]byte, Y: [32]byte - decompress pubkey A into components X, Y - **Cost**: Secp256k1=650; Secp256r1=2400 - Availability: v5 @@ -75,7 +75,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top - Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} -- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: [32]byte, Y: [32]byte - for (data A, recovery id B, signature C, D) recover a public key - **Cost**: 2000 - Availability: v5 @@ -175,7 +175,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -869,7 +869,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A, B: []byte → ..., any +- Stack: ..., A, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -879,7 +879,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -889,7 +889,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -899,7 +899,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -909,7 +909,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A, B: []byte, C → ... +- Stack: ..., A, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -919,7 +919,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -927,7 +927,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A, B: []byte → ... +- Stack: ..., A, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -939,7 +939,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application @@ -1154,7 +1154,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## bsqrt - Bytecode: 0x96 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: bigint → ..., bigint - The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers - **Cost**: 40 - Availability: v6 @@ -1171,7 +1171,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## sha3_256 - Bytecode: 0x98 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: []byte → ..., [32]byte - SHA3_256 hash of value A, yields [32]byte - **Cost**: 130 - Availability: v7 @@ -1253,7 +1253,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b% - Bytecode: 0xaa -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Stack: ..., A: bigint, B: bigint → ..., bigint - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 @@ -1446,7 +1446,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with - Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} -- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool +- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: [64]byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. - **Cost**: 5700 - Availability: v7 diff --git a/data/transactions/logic/TEAL_opcodes_v8.md b/data/transactions/logic/TEAL_opcodes_v8.md index b24a145086..f06e087e5e 100644 --- a/data/transactions/logic/TEAL_opcodes_v8.md +++ b/data/transactions/logic/TEAL_opcodes_v8.md @@ -43,7 +43,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} -- Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool +- Stack: ..., A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: Secp256k1=1700; Secp256r1=2500 - Availability: v5 @@ -64,7 +64,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} -- Stack: ..., A: []byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [33]byte → ..., X: [32]byte, Y: [32]byte - decompress pubkey A into components X, Y - **Cost**: Secp256k1=650; Secp256r1=2400 - Availability: v5 @@ -75,7 +75,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top - Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} -- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: [32]byte, Y: [32]byte - for (data A, recovery id B, signature C, D) recover a public key - **Cost**: 2000 - Availability: v5 @@ -175,7 +175,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -893,7 +893,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A, B: []byte → ..., any +- Stack: ..., A, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -903,7 +903,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -913,7 +913,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -923,7 +923,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -933,7 +933,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A, B: []byte, C → ... +- Stack: ..., A, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -943,7 +943,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -951,7 +951,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A, B: []byte → ... +- Stack: ..., A, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -963,7 +963,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application @@ -1251,7 +1251,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## bsqrt - Bytecode: 0x96 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: bigint → ..., bigint - The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers - **Cost**: 40 - Availability: v6 @@ -1268,7 +1268,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## sha3_256 - Bytecode: 0x98 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: []byte → ..., [32]byte - SHA3_256 hash of value A, yields [32]byte - **Cost**: 130 - Availability: v7 @@ -1350,7 +1350,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b% - Bytecode: 0xaa -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Stack: ..., A: bigint, B: bigint → ..., bigint - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 @@ -1605,7 +1605,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo - Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} -- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool +- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: [64]byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. - **Cost**: 5700 - Availability: v7 diff --git a/data/transactions/logic/TEAL_opcodes_v9.md b/data/transactions/logic/TEAL_opcodes_v9.md index f64d98931c..e14f4d7d76 100644 --- a/data/transactions/logic/TEAL_opcodes_v9.md +++ b/data/transactions/logic/TEAL_opcodes_v9.md @@ -43,7 +43,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Syntax: `ecdsa_verify V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x05 {uint8} -- Stack: ..., A: [32]byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., bool +- Stack: ..., A: [32]byte, B: [32]byte, C: [32]byte, D: [32]byte, E: [32]byte → ..., bool - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} - **Cost**: Secp256k1=1700; Secp256r1=2500 - Availability: v5 @@ -64,7 +64,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - Syntax: `ecdsa_pk_decompress V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x06 {uint8} -- Stack: ..., A: []byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [33]byte → ..., X: [32]byte, Y: [32]byte - decompress pubkey A into components X, Y - **Cost**: Secp256k1=650; Secp256r1=2400 - Availability: v5 @@ -75,7 +75,7 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top - Syntax: `ecdsa_pk_recover V` where V: [ECDSA](#field-group-ecdsa) - Bytecode: 0x07 {uint8} -- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: []byte, Y: []byte +- Stack: ..., A: [32]byte, B: uint64, C: [32]byte, D: [32]byte → ..., X: [32]byte, Y: [32]byte - for (data A, recovery id B, signature C, D) recover a public key - **Cost**: 2000 - Availability: v5 @@ -175,7 +175,7 @@ Overflow is an error condition which halts execution and fails the transaction. ## itob - Bytecode: 0x16 -- Stack: ..., A: uint64 → ..., []byte +- Stack: ..., A: uint64 → ..., [8]byte - converts uint64 A to big-endian byte array, always of length 8 ## btoi @@ -893,7 +893,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_local_get - Bytecode: 0x62 -- Stack: ..., A, B: []byte → ..., any +- Stack: ..., A, B: stateKey → ..., any - local state of the key B in the current application in account A - Availability: v2 - Mode: Application @@ -903,7 +903,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_get_ex - Bytecode: 0x63 -- Stack: ..., A, B: uint64, C: []byte → ..., X: any, Y: bool +- Stack: ..., A, B: uint64, C: stateKey → ..., X: any, Y: bool - X is the local state of application B, key C in account A. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -913,7 +913,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), _ava ## app_global_get - Bytecode: 0x64 -- Stack: ..., A: []byte → ..., any +- Stack: ..., A: stateKey → ..., any - global state of the key A in the current application - Availability: v2 - Mode: Application @@ -923,7 +923,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key ## app_global_get_ex - Bytecode: 0x65 -- Stack: ..., A: uint64, B: []byte → ..., X: any, Y: bool +- Stack: ..., A: uint64, B: stateKey → ..., X: any, Y: bool - X is the global state of application A, key B. Y is 1 if key existed, else 0 - Availability: v2 - Mode: Application @@ -933,7 +933,7 @@ params: Txn.ForeignApps offset (or, since v4, an _available_ application id), st ## app_local_put - Bytecode: 0x66 -- Stack: ..., A, B: []byte, C → ... +- Stack: ..., A, B: stateKey, C → ... - write C to key B in account A's local state of the current application - Availability: v2 - Mode: Application @@ -943,7 +943,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_global_put - Bytecode: 0x67 -- Stack: ..., A: []byte, B → ... +- Stack: ..., A: stateKey, B → ... - write B to key A in the global state of the current application - Availability: v2 - Mode: Application @@ -951,7 +951,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ account address), stat ## app_local_del - Bytecode: 0x68 -- Stack: ..., A, B: []byte → ... +- Stack: ..., A, B: stateKey → ... - delete key B from account A's local state of the current application - Availability: v2 - Mode: Application @@ -963,7 +963,7 @@ Deleting a key which is already absent has no effect on the application local st ## app_global_del - Bytecode: 0x69 -- Stack: ..., A: []byte → ... +- Stack: ..., A: stateKey → ... - delete key A from the global state of the current application - Availability: v2 - Mode: Application @@ -1251,7 +1251,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit ## bsqrt - Bytecode: 0x96 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: bigint → ..., bigint - The largest integer I such that I^2 <= A. A and I are interpreted as big-endian unsigned integers - **Cost**: 40 - Availability: v6 @@ -1268,7 +1268,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## sha3_256 - Bytecode: 0x98 -- Stack: ..., A: []byte → ..., []byte +- Stack: ..., A: []byte → ..., [32]byte - SHA3_256 hash of value A, yields [32]byte - **Cost**: 130 - Availability: v7 @@ -1350,7 +1350,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with ## b% - Bytecode: 0xaa -- Stack: ..., A: []byte, B: []byte → ..., []byte +- Stack: ..., A: bigint, B: bigint → ..., bigint - A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - Availability: v4 @@ -1605,7 +1605,7 @@ For boxes that exceed 4,096 bytes, consider `box_create`, `box_extract`, and `bo - Syntax: `vrf_verify S` where S: [vrf_verify](#field-group-vrf_verify) - Bytecode: 0xd0 {uint8} -- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: []byte, Y: bool +- Stack: ..., A: []byte, B: [80]byte, C: [32]byte → ..., X: [64]byte, Y: bool - Verify the proof B of message A against pubkey C. Returns vrf output and verification flag. - **Cost**: 5700 - Availability: v7 diff --git a/data/transactions/logic/crypto_test.go b/data/transactions/logic/crypto_test.go index bb6ef59c56..5aad230289 100644 --- a/data/transactions/logic/crypto_test.go +++ b/data/transactions/logic/crypto_test.go @@ -198,7 +198,7 @@ assert`, "int 1"}, {"falcon_verify", "", ` byte 0x62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd // msg // public key -byte 0xba00a5222fbaa5e2a1a61f708198a4dbc3de94b60d925042d9fa5a299ebb4be27156b1d86a174df4939342f11b776dffb8a0e874714f23318ca9acb823e9aeb14a926ed5cf76e736faa0b22e4bdedf7910decd19329f353b926ae4b404653dbc6db1885c010052b94675d4cc209ef2cf3cfe910c4ef51b6af16d8c7ab6651e57934ab19c89f771058f389ad80474740c529d59a3ea9ab9db228415fb9315dee23e8c7229398c4b0a2b7c5d6eff6e7d8cf1a776ae37f6272082796c0b2a0af637f7ce8fa7f1675dfbd1766543cbf3f19544d635298ea1fafe96ad1bb06fcc6ae9ff9c34acacb88653555c37f2ad6c4eb408478b0d2b6269841243f29b18fa8e0d5050f4f93878e53aac466dc4eb5a7194cb2213c26a2b8c7ccea06f89af26ae85315454da1b15952be639bb94fe3e99236291c4a1edfbe9faf8f32589bf47eb536b28e2cfbdea799d9cf4c88ef85ae45d451e1ab3431c247b796cbf12e63b737cc4894ad7a204f680a449cbbd2e86deca1069b3592977bd8ac7c5b5e1c1b436cde65177b6e82b2e666117a8e37b58122d1a31307ca112311e665b32c68bd42531b4e6bc79957d3d865f6470b8213db8175e5c7115f4ad520a4711b12d9004e661346c4da4cb3e95954ac58e075a320b862a6a317e0988d8fc376fb14562773b9d35d5a44ba951d866a3a06ac93a55e1a26fa91718db49a53e78d9e61d6120dfadd2b4929579ac56ccaac0f8e704826b55b4ca6d8020e42a6e62b5e41708e2e6848cd047385fa1df4f51733df35dbee25c96c4176eae332ca4df31c695fff8be31b4be62e63c3e049483c89384fb1d802e58db5514a59eb96e527b202d0cf45dc760fa0439afbc661868b9408e67254c8cf7c689c50d2f29bccd59c71ea7b6dd368de68669fdf889ac1f8cd390ea17894dd0538ff6e7c740bbf03b4fe32ad66c483c823548eea84f85826da44016bd8cdf2315b07a96a9737ebc7cb244547be3f759bdf50b467552c58333ed7e61cde799346bccc29d5d377d9d5364c369ffd88a83f90a699b3622184436b518e9196524ac9b55385b39ec099d9c18386e06b9dcad2499ddb9673cb87c652209ee60511c9249f1b7ab2b948b5e8b9115c218d5b793d65b96e2fc9e2c6c40ba63791bb89d7d96c33536ad7e6668a85e52ec7e1450a69f25766deeaeb41bcd249394b8ab65a286312db461c363cebe431c4dd5fd3b6bb5d26ae2c597799f400abb3ba160522e2e6da5ebd170a45c9ce80b135a5b330656aab26399bcacd857a7f237dfd2b14ecbfbcaabc7291ba78fe19ac2ecf005b66bb9771bf64f090269a2341967e79702733dc617b469ac12123faeb4c70d6fffac25f9fcd7dbd12ca363985b9bd845e939e6caf328e2bf8e53725bae94fbe30bfdbbc21e584ba72d7badbc2a0915c9faf9c69ad3703cf99a16399b38df157be8ec3a78f20d24b2131f9c25b3c1da70fc6c0c7aa9e9da3108368ca6211dcfa4988b1ace3c28b15b0559224570fbe3cde364734c7a66a0525c1d41e26788cd1c8a3888f344c9385804364f8430ca7d22338cc941917da3dc47a00aae13e3e972af49940c8fa179574694e369a3d5e67db6c91bf843151ca0fff512d9c322c690063ae9bd671815e9d03b3a841952ce04683509e415b8d5aebfcdbd6bd55efbffb2463cf2b96ccb8650a6cee732c8d4ce6409b9a747317866759553f1c5bcc392c98d14a034ccaaa6df5723bb88c38e80 +byte 0xba00a5222fbaa5e2a1a61f708198a4dbc3de94b60d925042d9fa5a299ebb4be27156b1d86a174df4939342f11b776dffb8a0e874714f23318ca9acb823e9aeb14a926ed5cf76e736faa0b22e4bdedf7910decd19329f353b926ae4b404653dbc6db1885c010052b94675d4cc209ef2cf3cfe910c4ef51b6af16d8c7ab6651e57934ab19c89f771058f389ad80474740c529d59a3ea9ab9db228415fb9315dee23e8c7229398c4b0a2b7c5d6eff6e7d8cf1a776ae37f6272082796c0b2a0af637f7ce8fa7f1675dfbd1766543cbf3f19544d635298ea1fafe96ad1bb06fcc6ae9ff9c34acacb88653555c37f2ad6c4eb408478b0d2b6269841243f29b18fa8e0d5050f4f93878e53aac466dc4eb5a7194cb2213c26a2b8c7ccea06f89af26ae85315454da1b15952be639bb94fe3e99236291c4a1edfbe9faf8f32589bf47eb536b28e2cfbdea799d9cf4c88ef85ae45d451e1ab3431c247b796cbf12e63b737cc4894ad7a204f680a449cbbd2e86deca1069b3592977bd8ac7c5b5e1c1b436cde65177b6e82b2e666117a8e37b58122d1a31307ca112311e665b32c68bd42531b4e6bc79957d3d865f6470b8213db8175e5c7115f4ad520a4711b12d9004e661346c4da4cb3e95954ac58e075a320b862a6a317e0988d8fc376fb14562773b9d35d5a44ba951d866a3a06ac93a55e1a26fa91718db49a53e78d9e61d6120dfadd2b4929579ac56ccaac0f8e704826b55b4ca6d8020e42a6e62b5e41708e2e6848cd047385fa1df4f51733df35dbee25c96c4176eae332ca4df31c695fff8be31b4be62e63c3e049483c89384fb1d802e58db5514a59eb96e527b202d0cf45dc760fa0439afbc661868b9408e67254c8cf7c689c50d2f29bccd59c71ea7b6dd368de68669fdf889ac1f8cd390ea17894dd0538ff6e7c740bbf03b4fe32ad66c483c823548eea84f85826da44016bd8cdf2315b07a96a9737ebc7cb244547be3f759bdf50b467552c58333ed7e61cde799346bccc29d5d377d9d5364c369ffd88a83f90a699b3622184436b518e9196524ac9b55385b39ec099d9c18386e06b9dcad2499ddb9673cb87c652209ee60511c9249f1b7ab2b948b5e8b9115c218d5b793d65b96e2fc9e2c6c40ba63791bb89d7d96c33536ad7e6668a85e52ec7e1450a69f25766deeaeb41bcd249394b8ab65a286312db461c363cebe431c4dd5fd3b6bb5d26ae2c597799f400abb3ba160522e2e6da5ebd170a45c9ce80b135a5b330656aab26399bcacd857a7f237dfd2b14ecbfbcaabc7291ba78fe19ac2ecf005b66bb9771bf64f090269a2341967e79702733dc617b469ac12123faeb4c70d6fffac25f9fcd7dbd12ca363985b9bd845e939e6caf328e2bf8e53725bae94fbe30bfdbbc21e584ba72d7badbc2a0915c9faf9c69ad3703cf99a16399b38df157be8ec3a78f20d24b2131f9c25b3c1da70fc6c0c7aa9e9da3108368ca6211dcfa4988b1ace3c28b15b0559224570fbe3cde364734c7a66a0525c1d41e26788cd1c8a3888f344c9385804364f8430ca7d22338cc941917da3dc47a00aae13e3e972af49940c8fa179574694e369a3d5e67db6c91bf843151ca0fff512d9c322c690063ae9bd671815e9d03b3a841952ce04683509e415b8d5aebfcdbd6bd55efbffb2463cf2b96ccb8650a6cee732c8d4ce6409b9a747317866759553f1c5bcc392c98d14a034ccaaa6df5723bb88c38e80 // sig byte 0x0a85ea3bb342a95a3941a4c2280c686729c76bc164092c0e203388460c556273e6f0a92640650c37e9d5b08fbd8d6bcca940acac9964e64a9e78bd28086b52898812264985e19c3d26318be2ec8852ca2ae2380746428cd08124cf792790d127d3dad09fe891cbadefef36269ae7d584b77ec428d794a6c3b7555956db00314d14a0aa14936830c8622623916639743b218243344224472240cfd158819190ede108394063c3df9c474eb16aa750e48663515d8229d3849670e30891142b632a6a282d915273a5f219d65ebe6b9e6c88170ac62c16a44895a950bfec82819221dab1358861bf0aa6b6342477016d50502a298840ddc42b3ade784b643c63c5e47993ada37dfdc0d56a1c7e4690b5a1d6485900b84f0b61425383b14d4b7ccc0abe8284a47a6f22050838b0482ad8ad389151c25e790ad670d5530f9b3dc518bb0a410f64346a74dc824238026daaa4ad97518d93670a48cf8f86ece593d23ab3a0d601d49a975db291f0d76263551e9f0b8a1b42396a27d9a122210330c692d5545d67c808b50560fc3d4933fa70c463513d7183e8aa091f34dd4426272620fe4b357deea710c687bb7a475d0ed0a40a26ae8f2a357e7a8fa5d5434050c1a36beaa7a90ee4db213a126db8151f2f4bbb4889d4e42bbd19f62dd7285def148071fb7f4f16b28c1d145d2e621fee275161a3d5b9319e7a59527c3d5c2838ef503e4166f2c22118b22bf80e8a1fc1bbbba00f231d2b1a8d3e592bdcc5fd40a2ecebb5ad27a51e7867715b54185a3e62951a5d808d80c31a59e6a3ca53a51eadc34c76dfd6aac22a6e805163b5e9ac8090869a9cd1e2972af7192bcd1da39c30f423ebc86d1976e8f52052262521d3b8ae7eb99d0ad623d811bac636f447e7dc9dcef6f52befd95861f1917116517b0e9b56a85967ab701ff8f1d4de443efce1b2a3d85b592df7a8c87814e8981575ef4e72757c5afa6bec4358e2f29966ad2830e4782f9a293351dfcaac1d0ca30ec1b5fd08a40a6e82938427a68641b96252a85443141c081982ba4d3c8ab05a1a545ea49c23ee07643ec5f013c2676db09cb834ef61817e615ad19c5829216026e5635dc13cad5ffb8bc267bf58d4ebbf100c3045e250c02c10772e96c580db049c80fdd3188e19ad893d16ac100052c557378416929319c9c262c21b768e6058a09b4e4800ae624c892117ec71504a283f558c623a212d048d5d401b00448b18ac25e1c99ab35d91f78badebcd651e86f3465ef99a0afa1721d2153e4a7b51d22b344a8dd102e7411abfe4bd5b8e2d62015edc08fc461fa90cfa666a9a42a0a86e11d6988913ba0259096cb846a1fd311c4cb693c4e3e1ed2ab57e2a5e0bd4616a79e22b28caa6d10dd09225e44bbdbfa1b7b23887055a90918220252777d5a620351cb013cc28346fc69d348165a39d03243a84a9c9bcd4d557a8e9607256baab893a0a5644520686be935e9ead84501f743a489a431cf10b8c27d3901c87b8771ce65e3130a7fe6ad62b709c23bbef1381b1ed49222f487db16af3c9d6779c01c986ea9f823be017fb8bce8e00f2b32840d54e8f656139a4c492257ee8743a8c5f51450c0366655e2b02d27619d07e556001430b04454891247813c8bc31bdee926d039a5038bfca8dc35e57789950442ad7ab3cfc031a8354bd9c462a37052d0b62066bcee0c292b890a71f4ea65895a7d837283404842c59f08414b20ec1b4fda6cc0c4d62216e8ead74ba90196168bc449a2050b442181ea57b915581bc387ed412e4cd5970fd0fb83c94fbbf960d05ffe6d0a26171c249809604a0b2b411e2d6622145c936e31258baf2b7d3c413a9a1d67bc4026d01b47a10b6c5b87f6a36ba1cedd681ca55b9c042bf9afcfcb636040793e08158dd877c49c16658f819129e26237427a1d80b941fbabb4abd4f1da0b6d428a59fbc450620eeb1651849e5972fb12e6dc8092a9fda70206a48d9dc2645641a147626350cf45b1a7d57724fcab0a594df7c023928a3c7a2fc3c9d33e9af10ae5ed282c475a611671d20d90752f2a28db48b7e5d9184212432fa948fbc885f866c93a0b7f510329aea4d53ecf9482f42974beaf289086afdb4797aa129d10639948f46a805ea4000cf1554505f4bd9d775d5894da115f5840913d5070c860b3a623eb261f5f928a31cbcec17c4274b5d1b28fdb231cc8f606c9dc324db5c12f97518fd03466541f7881762c25d711976c6d4f9271d29fa51dc263f650a32010343a51e7dab344e2f6d768864072ddb5df58486434998a280aad94886ea7a11132184e6274d4cd59a5deabf8a4dbbe29e9c234a52d3972608d0a3ea92a78e08531bb938384444246be5bc594ed4d06168e870924e8913f8242bd35f7c9d5ee238cb6db17496047acce0183f2d10a4cf2bbc8e39daf44e630393a0473b8983863b1998c17026ff35ec32a8058fd603ec369b80a94cb7b555cb469f6468de3909b21293b8d0a53a5c813d218d7c630f4d47bb1eb88253e6e1af721ba8a4453e falcon_verify @@ -431,7 +431,7 @@ byte 0x%s if test.pass { testAccepts(t, src, 5) } else { - testPanics(t, src, 5) + testPanics(t, notrack(src), 5) } }) } @@ -473,7 +473,10 @@ byte 0x%s } // coverage for pk length check - testPanics(t, `int 31; bzero; byte 0x; byte 0x; byte 0x; byte 0x; ecdsa_verify Secp256k1`, 5, "must be 32") + testPanics(t, notrack(`int 31; bzero; byte 0x; byte 0x; byte 0x; byte 0x; ecdsa_verify Secp256k1`), 5, "must be 32") + + // we did not implement arg length checks for x,y & r,s, so we must simply fail to verify, not panic + testAccepts(t, notrack(`int 32; bzero; byte 0x; byte 0x; byte 0x; byte 0x; ecdsa_verify Secp256k1; !`), 5) // ecdsa recover tests source = ` @@ -583,7 +586,7 @@ byte 0x%s if test.pass { testAccepts(t, src, fidoVersion) } else { - testPanics(t, src, fidoVersion) + testPanics(t, notrack(src), fidoVersion) } }) } @@ -685,11 +688,8 @@ func TestEcdsaCostVariation(t *testing.T) { // Doesn't matter if the actual verify returns true or false. Just confirm the cost depends on curve. source := ` -global ZeroAddress // need 32 bytes -byte "signature r" -byte "signature s" -byte "PK x" -byte "PK y" +global ZeroAddress // need 32 bytes for all 5 args +dup; dup; dup; dup; ecdsa_verify Secp256k1 ! assert @@ -700,11 +700,8 @@ int ` + fmt.Sprintf("%d", testLogicBudget-1700-8) + ` testAccepts(t, source, 6) // Secp256k1 was 5, but OpcodeBudget is 6 source = ` -global ZeroAddress // need 32 bytes -byte "signature r" -byte "signature s" -byte "PK x" -byte "PK y" +global ZeroAddress // need 32 bytes for all 5 args +dup; dup; dup; dup ecdsa_verify Secp256r1 ! assert diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index bd56b9b01b..210d7a3164 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -28,6 +28,7 @@ import ( "math/big" "math/bits" "runtime" + "strconv" "strings" "golang.org/x/exp/slices" @@ -798,7 +799,7 @@ var ( // AllStackTypes is a map of all the stack types we recognize // so that we can iterate over them in doc prep // and use them for opcode proto shorthand - AllStackTypes = map[rune]StackType{ + AllStackTypes = map[byte]StackType{ 'a': StackAny, 'b': StackBytes, 'i': StackUint64, @@ -806,9 +807,6 @@ var ( 'A': StackAddress, 'I': StackBigInt, 'T': StackBoolean, - '3': StackBytes32, - '6': StackBytes64, - '8': StackBytes80, 'M': StackMethodSelector, 'K': StackStateKey, 'N': StackBoxName, @@ -946,13 +944,31 @@ func parseStackTypes(spec string) StackTypes { if spec == "" { return nil } - types := make(StackTypes, len(spec)) - for i, letter := range spec { + types := make(StackTypes, 0, len(spec)) + for i := 0; i < len(spec); i++ { + letter := spec[i] + if letter == '{' { + if types[len(types)-1] != StackBytes { + panic("{ after non-bytes " + spec) + } + end := strings.IndexByte(spec[i:], '}') + if end == -1 { + panic("No } after b{ " + spec) + } + size, err := strconv.Atoi(spec[i+1 : i+end]) + if err != nil { + panic("b{} does not contain a number " + spec) + } + // replace the generic type with the constrained type + types[len(types)-1] = NewStackType(avmBytes, static(uint64(size)), fmt.Sprintf("[%d]byte", size)) + i += end + continue + } st, ok := AllStackTypes[letter] if !ok { panic(spec) } - types[i] = st + types = append(types, st) } return types } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 12d1b8c281..91ad91d1fe 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -1991,12 +1991,12 @@ func TestAppLocalGlobalErrorCases(t *testing.T) { if ep.Proto.LogicSigVersion < directRefEnabledVersion { sender = "int 0;" } - testApp(t, fmt.Sprintf(`byte "%v"; int 1;`+g+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") + testApp(t, notrack(fmt.Sprintf(`byte "%v"; int 1;`+g+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1))), ep, "key too long") testApp(t, fmt.Sprintf(`byte "%v"; int 1;`+g+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) ledger.NewLocals(tx.Sender, 888) - testApp(t, fmt.Sprintf(sender+`byte "%v"; int 1;`+l+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1)), ep, "key too long") + testApp(t, notrack(fmt.Sprintf(sender+`byte "%v"; int 1;`+l+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen+1))), ep, "key too long") testApp(t, fmt.Sprintf(sender+`byte "%v"; int 1;`+l+`int 1`, strings.Repeat("v", ep.Proto.MaxAppKeyLen)), ep) @@ -3214,8 +3214,7 @@ func TestReturnTypes(t *testing.T) { "err": true, "return": true, - // these have unusual input size requirements not encoded in proto - "falcon_verify": true, + // panics unless the pk is proper "ecdsa_pk_decompress": true, "frame_dig": true, // would need a "proto" subroutine diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index b7c55faf87..3ede2c134b 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -5098,8 +5098,8 @@ func TestBytesMath(t *testing.T) { testAccepts(t, "byte 0xffffff; bsqrt; len; int 2; ==; return", 6) // 64 byte long inputs are accepted, even if they produce longer outputs testAccepts(t, fmt.Sprintf("byte 0x%s; bsqrt; len; int 32; ==", effs), 6) - // 65 byte inputs are not ok. - testPanics(t, fmt.Sprintf("byte 0x%s00; bsqrt; pop; int 1", effs), 6) + // 65 byte inputs are not ok (no track allows assembly) + testPanics(t, notrack(fmt.Sprintf("byte 0x%s00; bsqrt; pop; int 1", effs)), 6) } func TestBytesCompare(t *testing.T) { diff --git a/data/transactions/logic/langspec_v1.json b/data/transactions/logic/langspec_v1.json index 6839e40926..5fd347507d 100644 --- a/data/transactions/logic/langspec_v1.json +++ b/data/transactions/logic/langspec_v1.json @@ -2,33 +2,6 @@ "Version": 1, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -463,7 +436,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json index d971b27154..f59103f0f6 100644 --- a/data/transactions/logic/langspec_v10.json +++ b/data/transactions/logic/langspec_v10.json @@ -2,33 +2,6 @@ "Version": 10, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -208,10 +181,10 @@ "Name": "ecdsa_verify", "Args": [ "[32]byte", - "[]byte", - "[]byte", - "[]byte", - "[]byte" + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte" ], "Returns": [ "bool" @@ -241,11 +214,11 @@ "Opcode": 6, "Name": "ecdsa_pk_decompress", "Args": [ - "[]byte" + "[33]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -278,8 +251,8 @@ "[32]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -562,7 +535,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -2594,7 +2567,7 @@ "Name": "app_local_get", "Args": [ "any", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2614,7 +2587,7 @@ "Args": [ "any", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2633,7 +2606,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2652,7 +2625,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2672,7 +2645,7 @@ "Name": "app_local_put", "Args": [ "any", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2688,7 +2661,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2704,7 +2677,7 @@ "Name": "app_local_del", "Args": [ "any", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2719,7 +2692,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -3292,10 +3265,10 @@ "Opcode": 150, "Name": "bsqrt", "Args": [ - "[]byte" + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "40", @@ -3332,7 +3305,7 @@ "[]byte" ], "Returns": [ - "[]byte" + "[32]byte" ], "Size": 1, "DocCost": "130", @@ -3526,11 +3499,11 @@ "Opcode": 170, "Name": "b%", "Args": [ - "[]byte", - "[]byte" + "bigint", + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "20", @@ -4590,7 +4563,7 @@ "[32]byte" ], "Returns": [ - "[]byte", + "[64]byte", "bool" ], "Size": 2, diff --git a/data/transactions/logic/langspec_v2.json b/data/transactions/logic/langspec_v2.json index a832f86643..24f9ad97f7 100644 --- a/data/transactions/logic/langspec_v2.json +++ b/data/transactions/logic/langspec_v2.json @@ -2,33 +2,6 @@ "Version": 2, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -463,7 +436,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -1533,7 +1506,7 @@ "Name": "app_local_get", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -1553,7 +1526,7 @@ "Args": [ "uint64", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -1572,7 +1545,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -1591,7 +1564,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -1611,7 +1584,7 @@ "Name": "app_local_put", "Args": [ "uint64", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -1627,7 +1600,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -1643,7 +1616,7 @@ "Name": "app_local_del", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -1658,7 +1631,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", diff --git a/data/transactions/logic/langspec_v3.json b/data/transactions/logic/langspec_v3.json index f1566b5289..085084359a 100644 --- a/data/transactions/logic/langspec_v3.json +++ b/data/transactions/logic/langspec_v3.json @@ -2,33 +2,6 @@ "Version": 3, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -463,7 +436,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -1910,7 +1883,7 @@ "Name": "app_local_get", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -1930,7 +1903,7 @@ "Args": [ "uint64", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -1949,7 +1922,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -1968,7 +1941,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -1988,7 +1961,7 @@ "Name": "app_local_put", "Args": [ "uint64", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2004,7 +1977,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2020,7 +1993,7 @@ "Name": "app_local_del", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2035,7 +2008,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", diff --git a/data/transactions/logic/langspec_v4.json b/data/transactions/logic/langspec_v4.json index 49591c10f6..ddbf651876 100644 --- a/data/transactions/logic/langspec_v4.json +++ b/data/transactions/logic/langspec_v4.json @@ -2,33 +2,6 @@ "Version": 4, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -463,7 +436,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -2032,7 +2005,7 @@ "Name": "app_local_get", "Args": [ "any", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2052,7 +2025,7 @@ "Args": [ "any", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2071,7 +2044,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2090,7 +2063,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2110,7 +2083,7 @@ "Name": "app_local_put", "Args": [ "any", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2126,7 +2099,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2142,7 +2115,7 @@ "Name": "app_local_del", "Args": [ "any", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2157,7 +2130,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2642,11 +2615,11 @@ "Opcode": 170, "Name": "b%", "Args": [ - "[]byte", - "[]byte" + "bigint", + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "20", diff --git a/data/transactions/logic/langspec_v5.json b/data/transactions/logic/langspec_v5.json index ccde5509fc..2cca3941da 100644 --- a/data/transactions/logic/langspec_v5.json +++ b/data/transactions/logic/langspec_v5.json @@ -2,33 +2,6 @@ "Version": 5, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -208,10 +181,10 @@ "Name": "ecdsa_verify", "Args": [ "[32]byte", - "[]byte", - "[]byte", - "[]byte", - "[]byte" + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte" ], "Returns": [ "bool" @@ -240,11 +213,11 @@ "Opcode": 6, "Name": "ecdsa_pk_decompress", "Args": [ - "[]byte" + "[33]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -276,8 +249,8 @@ "[32]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -559,7 +532,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -2350,7 +2323,7 @@ "Name": "app_local_get", "Args": [ "any", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2370,7 +2343,7 @@ "Args": [ "any", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2389,7 +2362,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2408,7 +2381,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2428,7 +2401,7 @@ "Name": "app_local_put", "Args": [ "any", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2444,7 +2417,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2460,7 +2433,7 @@ "Name": "app_local_del", "Args": [ "any", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2475,7 +2448,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -3011,11 +2984,11 @@ "Opcode": 170, "Name": "b%", "Args": [ - "[]byte", - "[]byte" + "bigint", + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "20", diff --git a/data/transactions/logic/langspec_v6.json b/data/transactions/logic/langspec_v6.json index 72e1d9c64f..6795706db7 100644 --- a/data/transactions/logic/langspec_v6.json +++ b/data/transactions/logic/langspec_v6.json @@ -2,33 +2,6 @@ "Version": 6, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -208,10 +181,10 @@ "Name": "ecdsa_verify", "Args": [ "[32]byte", - "[]byte", - "[]byte", - "[]byte", - "[]byte" + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte" ], "Returns": [ "bool" @@ -240,11 +213,11 @@ "Opcode": 6, "Name": "ecdsa_pk_decompress", "Args": [ - "[]byte" + "[33]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -276,8 +249,8 @@ "[32]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -559,7 +532,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -2368,7 +2341,7 @@ "Name": "app_local_get", "Args": [ "any", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2388,7 +2361,7 @@ "Args": [ "any", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2407,7 +2380,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2426,7 +2399,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2446,7 +2419,7 @@ "Name": "app_local_put", "Args": [ "any", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2462,7 +2435,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2478,7 +2451,7 @@ "Name": "app_local_del", "Args": [ "any", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2493,7 +2466,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2885,10 +2858,10 @@ "Opcode": 150, "Name": "bsqrt", "Args": [ - "[]byte" + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "40", @@ -3102,11 +3075,11 @@ "Opcode": 170, "Name": "b%", "Args": [ - "[]byte", - "[]byte" + "bigint", + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "20", diff --git a/data/transactions/logic/langspec_v7.json b/data/transactions/logic/langspec_v7.json index 7229ee534c..a7b4df95b3 100644 --- a/data/transactions/logic/langspec_v7.json +++ b/data/transactions/logic/langspec_v7.json @@ -2,33 +2,6 @@ "Version": 7, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -208,10 +181,10 @@ "Name": "ecdsa_verify", "Args": [ "[32]byte", - "[]byte", - "[]byte", - "[]byte", - "[]byte" + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte" ], "Returns": [ "bool" @@ -241,11 +214,11 @@ "Opcode": 6, "Name": "ecdsa_pk_decompress", "Args": [ - "[]byte" + "[33]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -278,8 +251,8 @@ "[32]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -562,7 +535,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -2528,7 +2501,7 @@ "Name": "app_local_get", "Args": [ "any", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2548,7 +2521,7 @@ "Args": [ "any", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2567,7 +2540,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2586,7 +2559,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2606,7 +2579,7 @@ "Name": "app_local_put", "Args": [ "any", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2622,7 +2595,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2638,7 +2611,7 @@ "Name": "app_local_del", "Args": [ "any", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2653,7 +2626,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -3064,10 +3037,10 @@ "Opcode": 150, "Name": "bsqrt", "Args": [ - "[]byte" + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "40", @@ -3104,7 +3077,7 @@ "[]byte" ], "Returns": [ - "[]byte" + "[32]byte" ], "Size": 1, "DocCost": "130", @@ -3298,11 +3271,11 @@ "Opcode": 170, "Name": "b%", "Args": [ - "[]byte", - "[]byte" + "bigint", + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "20", @@ -4238,7 +4211,7 @@ "[32]byte" ], "Returns": [ - "[]byte", + "[64]byte", "bool" ], "Size": 2, diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json index 2f1576864a..186f9cdfe2 100644 --- a/data/transactions/logic/langspec_v8.json +++ b/data/transactions/logic/langspec_v8.json @@ -2,33 +2,6 @@ "Version": 8, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -208,10 +181,10 @@ "Name": "ecdsa_verify", "Args": [ "[32]byte", - "[]byte", - "[]byte", - "[]byte", - "[]byte" + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte" ], "Returns": [ "bool" @@ -241,11 +214,11 @@ "Opcode": 6, "Name": "ecdsa_pk_decompress", "Args": [ - "[]byte" + "[33]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -278,8 +251,8 @@ "[32]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -562,7 +535,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -2588,7 +2561,7 @@ "Name": "app_local_get", "Args": [ "any", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2608,7 +2581,7 @@ "Args": [ "any", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2627,7 +2600,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2646,7 +2619,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2666,7 +2639,7 @@ "Name": "app_local_put", "Args": [ "any", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2682,7 +2655,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2698,7 +2671,7 @@ "Name": "app_local_del", "Args": [ "any", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2713,7 +2686,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -3286,10 +3259,10 @@ "Opcode": 150, "Name": "bsqrt", "Args": [ - "[]byte" + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "40", @@ -3326,7 +3299,7 @@ "[]byte" ], "Returns": [ - "[]byte" + "[32]byte" ], "Size": 1, "DocCost": "130", @@ -3520,11 +3493,11 @@ "Opcode": 170, "Name": "b%", "Args": [ - "[]byte", - "[]byte" + "bigint", + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "20", @@ -4584,7 +4557,7 @@ "[32]byte" ], "Returns": [ - "[]byte", + "[64]byte", "bool" ], "Size": 2, diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json index 3ec2d39af7..cab0aa6d91 100644 --- a/data/transactions/logic/langspec_v9.json +++ b/data/transactions/logic/langspec_v9.json @@ -2,33 +2,6 @@ "Version": 9, "LogicSigVersion": 10, "NamedTypes": [ - { - "Name": "[32]byte", - "Abbreviation": "3", - "Bound": [ - 32, - 32 - ], - "AVMType": "[]byte" - }, - { - "Name": "[64]byte", - "Abbreviation": "6", - "Bound": [ - 64, - 64 - ], - "AVMType": "[]byte" - }, - { - "Name": "[80]byte", - "Abbreviation": "8", - "Bound": [ - 80, - 80 - ], - "AVMType": "[]byte" - }, { "Name": "[]byte", "Abbreviation": "b", @@ -208,10 +181,10 @@ "Name": "ecdsa_verify", "Args": [ "[32]byte", - "[]byte", - "[]byte", - "[]byte", - "[]byte" + "[32]byte", + "[32]byte", + "[32]byte", + "[32]byte" ], "Returns": [ "bool" @@ -241,11 +214,11 @@ "Opcode": 6, "Name": "ecdsa_pk_decompress", "Args": [ - "[]byte" + "[33]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -278,8 +251,8 @@ "[32]byte" ], "Returns": [ - "[]byte", - "[]byte" + "[32]byte", + "[32]byte" ], "Size": 2, "ArgEnum": [ @@ -562,7 +535,7 @@ "uint64" ], "Returns": [ - "[]byte" + "[8]byte" ], "Size": 1, "DocCost": "1", @@ -2588,7 +2561,7 @@ "Name": "app_local_get", "Args": [ "any", - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2608,7 +2581,7 @@ "Args": [ "any", "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2627,7 +2600,7 @@ "Opcode": 100, "Name": "app_global_get", "Args": [ - "[]byte" + "stateKey" ], "Returns": [ "any" @@ -2646,7 +2619,7 @@ "Name": "app_global_get_ex", "Args": [ "uint64", - "[]byte" + "stateKey" ], "Returns": [ "any", @@ -2666,7 +2639,7 @@ "Name": "app_local_put", "Args": [ "any", - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2682,7 +2655,7 @@ "Opcode": 103, "Name": "app_global_put", "Args": [ - "[]byte", + "stateKey", "any" ], "Size": 1, @@ -2698,7 +2671,7 @@ "Name": "app_local_del", "Args": [ "any", - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -2713,7 +2686,7 @@ "Opcode": 105, "Name": "app_global_del", "Args": [ - "[]byte" + "stateKey" ], "Size": 1, "DocCost": "1", @@ -3286,10 +3259,10 @@ "Opcode": 150, "Name": "bsqrt", "Args": [ - "[]byte" + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "40", @@ -3326,7 +3299,7 @@ "[]byte" ], "Returns": [ - "[]byte" + "[32]byte" ], "Size": 1, "DocCost": "130", @@ -3520,11 +3493,11 @@ "Opcode": 170, "Name": "b%", "Args": [ - "[]byte", - "[]byte" + "bigint", + "bigint" ], "Returns": [ - "[]byte" + "bigint" ], "Size": 1, "DocCost": "20", @@ -4584,7 +4557,7 @@ "[32]byte" ], "Returns": [ - "[]byte", + "[64]byte", "bool" ], "Size": 2, diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 18af2af1b2..b8ecd76cac 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -488,34 +488,34 @@ func (spec *OpSpec) deadens() bool { // assembly-time, with ops.returns() var OpSpecs = []OpSpec{ {0x00, "err", opErr, proto(":x"), 1, detDefault()}, - {0x01, "sha256", opSHA256, proto("b:3"), 1, costly(7)}, - {0x02, "keccak256", opKeccak256, proto("b:3"), 1, costly(26)}, - {0x03, "sha512_256", opSHA512_256, proto("b:3"), 1, costly(9)}, + {0x01, "sha256", opSHA256, proto("b:b{32}"), 1, costly(7)}, + {0x02, "keccak256", opKeccak256, proto("b:b{32}"), 1, costly(26)}, + {0x03, "sha512_256", opSHA512_256, proto("b:b{32}"), 1, costly(9)}, // Cost of these opcodes increases in AVM version 2 based on measured // performance. Should be able to run max hashes during stateful TEAL // and achieve reasonable TPS. Same opcode for different versions // is OK. - {0x01, "sha256", opSHA256, proto("b:3"), 2, costly(35)}, - {0x02, "keccak256", opKeccak256, proto("b:3"), 2, costly(130)}, - {0x03, "sha512_256", opSHA512_256, proto("b:3"), 2, costly(45)}, + {0x01, "sha256", opSHA256, proto("b:b{32}"), 2, costly(35)}, + {0x02, "keccak256", opKeccak256, proto("b:b{32}"), 2, costly(130)}, + {0x03, "sha512_256", opSHA512_256, proto("b:b{32}"), 2, costly(45)}, /* Tabling these changes until we offer unlimited global storage as there is currently a useful pattern that requires hashes on long slices to creating logicsigs in apps. - {0x01, "sha256", opSHA256, proto("b:b"), ?, costByLength(...)}, - {0x02, "keccak256", opKeccak256, proto("b:b"), ?, costByLength(...)}, - {0x03, "sha512_256", opSHA512_256, proto("b:b"), ?, costByLength(...)}, + {0x01, "sha256", opSHA256, proto("b:b{32}"), ?, costByLength(...)}, + {0x02, "keccak256", opKeccak256, proto("b:b{32}"), ?, costByLength(...)}, + {0x03, "sha512_256", opSHA512_256, proto("b:b{32}"), ?, costByLength(...)}, */ - {0x04, "ed25519verify", opEd25519Verify, proto("b63:T"), 1, costly(1900).only(ModeSig)}, - {0x04, "ed25519verify", opEd25519Verify, proto("b63:T"), 5, costly(1900)}, + {0x04, "ed25519verify", opEd25519Verify, proto("bb{64}b{32}:T"), 1, costly(1900).only(ModeSig)}, + {0x04, "ed25519verify", opEd25519Verify, proto("bb{64}b{32}:T"), 5, costly(1900)}, - {0x05, "ecdsa_verify", opEcdsaVerify, proto("3bbbb:T"), 5, costByField("v", &EcdsaCurves, ecdsaVerifyCosts)}, - {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, proto("b:bb"), 5, costByField("v", &EcdsaCurves, ecdsaDecompressCosts)}, - {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, proto("3i33:bb"), 5, field("v", &EcdsaCurves).costs(2000)}, + {0x05, "ecdsa_verify", opEcdsaVerify, proto("b{32}b{32}b{32}b{32}b{32}:T"), 5, costByField("v", &EcdsaCurves, ecdsaVerifyCosts)}, + {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, proto("b{33}:b{32}b{32}"), 5, costByField("v", &EcdsaCurves, ecdsaDecompressCosts)}, + {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, proto("b{32}ib{32}b{32}:b{32}b{32}"), 5, field("v", &EcdsaCurves).costs(2000)}, {0x08, "+", opPlus, proto("ii:i"), 1, detDefault()}, {0x09, "-", opMinus, proto("ii:i"), 1, detDefault()}, @@ -531,7 +531,7 @@ var OpSpecs = []OpSpec{ {0x13, "!=", opNeq, proto("aa:T"), 1, typed(typeEquals)}, {0x14, "!", opNot, proto("i:i"), 1, detDefault()}, {0x15, "len", opLen, proto("b:i"), 1, detDefault()}, - {0x16, "itob", opItob, proto("i:b"), 1, detDefault()}, + {0x16, "itob", opItob, proto("i:b{8}"), 1, detDefault()}, {0x17, "btoi", opBtoi, proto("b:i"), 1, detDefault()}, {0x18, "%", opModulo, proto("ii:i"), 1, detDefault()}, {0x19, "|", opBitOr, proto("ii:i"), 1, detDefault()}, @@ -621,18 +621,18 @@ var OpSpecs = []OpSpec{ {0x60, "balance", opBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, {0x61, "app_opted_in", opAppOptedIn, proto("ii:T"), 2, only(ModeApp)}, {0x61, "app_opted_in", opAppOptedIn, proto("ai:T"), directRefEnabledVersion, only(ModeApp)}, - {0x62, "app_local_get", opAppLocalGet, proto("ib:a").appStateExplain(opAppLocalGetStateChange), 2, only(ModeApp)}, - {0x62, "app_local_get", opAppLocalGet, proto("ab:a").appStateExplain(opAppLocalGetStateChange), directRefEnabledVersion, only(ModeApp)}, - {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iib:aT").appStateExplain(opAppLocalGetExStateChange), 2, only(ModeApp)}, - {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aib:aT").appStateExplain(opAppLocalGetExStateChange), directRefEnabledVersion, only(ModeApp)}, - {0x64, "app_global_get", opAppGlobalGet, proto("b:a").appStateExplain(opAppGlobalGetStateChange), 2, only(ModeApp)}, - {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("ib:aT").appStateExplain(opAppGlobalGetExStateChange), 2, only(ModeApp)}, - {0x66, "app_local_put", opAppLocalPut, proto("iba:").appStateExplain(opAppLocalPutStateChange), 2, only(ModeApp)}, - {0x66, "app_local_put", opAppLocalPut, proto("aba:").appStateExplain(opAppLocalPutStateChange), directRefEnabledVersion, only(ModeApp)}, - {0x67, "app_global_put", opAppGlobalPut, proto("ba:").appStateExplain(opAppGlobalPutStateChange), 2, only(ModeApp)}, - {0x68, "app_local_del", opAppLocalDel, proto("ib:").appStateExplain(opAppLocalDelStateChange), 2, only(ModeApp)}, - {0x68, "app_local_del", opAppLocalDel, proto("ab:").appStateExplain(opAppLocalDelStateChange), directRefEnabledVersion, only(ModeApp)}, - {0x69, "app_global_del", opAppGlobalDel, proto("b:").appStateExplain(opAppGlobalDelStateChange), 2, only(ModeApp)}, + {0x62, "app_local_get", opAppLocalGet, proto("iK:a").appStateExplain(opAppLocalGetStateChange), 2, only(ModeApp)}, + {0x62, "app_local_get", opAppLocalGet, proto("aK:a").appStateExplain(opAppLocalGetStateChange), directRefEnabledVersion, only(ModeApp)}, + {0x63, "app_local_get_ex", opAppLocalGetEx, proto("iiK:aT").appStateExplain(opAppLocalGetExStateChange), 2, only(ModeApp)}, + {0x63, "app_local_get_ex", opAppLocalGetEx, proto("aiK:aT").appStateExplain(opAppLocalGetExStateChange), directRefEnabledVersion, only(ModeApp)}, + {0x64, "app_global_get", opAppGlobalGet, proto("K:a").appStateExplain(opAppGlobalGetStateChange), 2, only(ModeApp)}, + {0x65, "app_global_get_ex", opAppGlobalGetEx, proto("iK:aT").appStateExplain(opAppGlobalGetExStateChange), 2, only(ModeApp)}, + {0x66, "app_local_put", opAppLocalPut, proto("iKa:").appStateExplain(opAppLocalPutStateChange), 2, only(ModeApp)}, + {0x66, "app_local_put", opAppLocalPut, proto("aKa:").appStateExplain(opAppLocalPutStateChange), directRefEnabledVersion, only(ModeApp)}, + {0x67, "app_global_put", opAppGlobalPut, proto("Ka:").appStateExplain(opAppGlobalPutStateChange), 2, only(ModeApp)}, + {0x68, "app_local_del", opAppLocalDel, proto("iK:").appStateExplain(opAppLocalDelStateChange), 2, only(ModeApp)}, + {0x68, "app_local_del", opAppLocalDel, proto("aK:").appStateExplain(opAppLocalDelStateChange), directRefEnabledVersion, only(ModeApp)}, + {0x69, "app_global_del", opAppGlobalDel, proto("K:").appStateExplain(opAppGlobalDelStateChange), 2, only(ModeApp)}, {0x70, "asset_holding_get", opAssetHoldingGet, proto("ii:aT"), 2, field("f", &AssetHoldingFields).only(ModeApp)}, {0x70, "asset_holding_get", opAssetHoldingGet, proto("ai:aT"), directRefEnabledVersion, field("f", &AssetHoldingFields).only(ModeApp)}, {0x71, "asset_params_get", opAssetParamsGet, proto("i:aT"), 2, field("f", &AssetParamsFields).only(ModeApp)}, @@ -648,9 +648,9 @@ var OpSpecs = []OpSpec{ {0x82, "pushbytess", opPushBytess, proto(":", "", "[N items]").stackExplain(opPushBytessStackChange), 8, constants(asmPushBytess, checkByteImmArgs, "bytes ...", immBytess).typed(typePushBytess).trust()}, {0x83, "pushints", opPushInts, proto(":", "", "[N items]").stackExplain(opPushIntsStackChange), 8, constants(asmPushInts, checkIntImmArgs, "uint ...", immInts).typed(typePushInts).trust()}, - {0x84, "ed25519verify_bare", opEd25519VerifyBare, proto("b63:T"), 7, costly(1900)}, - {0x85, "falcon_verify", opFalconVerify, proto("bbb:T"), spOpcodesVersion, costly(1700)}, // dynamic for internal hash? - {0x86, "sumhash512", opSumhash512, proto("b:6"), spOpcodesVersion, costByLength(150, 7, 4, 0)}, + {0x84, "ed25519verify_bare", opEd25519VerifyBare, proto("bb{64}b{32}:T"), 7, costly(1900)}, + {0x85, "falcon_verify", opFalconVerify, proto("bb{1232}b{1793}:T"), spOpcodesVersion, costly(1700)}, // dynamic for internal hash? + {0x86, "sumhash512", opSumhash512, proto("b:b{64}"), spOpcodesVersion, costByLength(150, 7, 4, 0)}, // "Function oriented" {0x88, "callsub", opCallSub, proto(":"), 4, detBranch()}, @@ -669,11 +669,11 @@ var OpSpecs = []OpSpec{ {0x93, "bitlen", opBitLen, proto("a:i"), 4, detDefault()}, {0x94, "exp", opExp, proto("ii:i"), 4, detDefault()}, {0x95, "expw", opExpw, proto("ii:ii"), 4, costly(10)}, - {0x96, "bsqrt", opBytesSqrt, proto("b:b"), 6, costly(40)}, + {0x96, "bsqrt", opBytesSqrt, proto("I:I"), 6, costly(40)}, {0x97, "divw", opDivw, proto("iii:i"), 6, detDefault()}, - {0x98, "sha3_256", opSHA3_256, proto("b:b"), 7, costly(130)}, + {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), 7, costly(130)}, /* Will end up following keccak256 - - {0x98, "sha3_256", opSHA3_256, proto("b:b"), ?, costByLength(...)},}, + {0x98, "sha3_256", opSHA3_256, proto("b:b{32}"), ?, costByLength(...)},}, */ // Byteslice math. @@ -687,7 +687,7 @@ var OpSpecs = []OpSpec{ {0xa7, "b>=", opBytesGe, proto("II:T"), 4, detDefault()}, {0xa8, "b==", opBytesEq, proto("II:T"), 4, detDefault()}, {0xa9, "b!=", opBytesNeq, proto("II:T"), 4, detDefault()}, - {0xaa, "b%", opBytesModulo, proto("bb:b"), 4, costly(20)}, + {0xaa, "b%", opBytesModulo, proto("II:I"), 4, costly(20)}, {0xab, "b|", opBytesBitOr, proto("bb:b"), 4, costly(6)}, {0xac, "b&", opBytesBitAnd, proto("bb:b"), 4, costly(6)}, {0xad, "b^", opBytesBitXor, proto("bb:b"), 4, costly(6)}, @@ -724,7 +724,7 @@ var OpSpecs = []OpSpec{ {0xc6, "gitxnas", opGitxnas, proto("i:a"), 6, immediates("t", "f").field("f", &TxnArrayFields).only(ModeApp)}, // randomness support - {0xd0, "vrf_verify", opVrfVerify, proto("b83:bT"), randomnessVersion, field("s", &VrfStandards).costs(5700)}, + {0xd0, "vrf_verify", opVrfVerify, proto("bb{80}b{32}:b{64}T"), randomnessVersion, field("s", &VrfStandards).costs(5700)}, {0xd1, "block", opBlock, proto("i:a"), randomnessVersion, field("f", &BlockFields)}, {0xd2, "box_splice", opBoxSplice, proto("Niib:").appStateExplain(opBoxSpliceStateChange), spliceVersion, only(ModeApp)}, {0xd3, "box_resize", opBoxResize, proto("Ni:").appStateExplain(opBoxResizeStateChange), spliceVersion, only(ModeApp)}, From 877090be830c2003ff9e44b56ed5a8f355258ae5 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:02:29 -0500 Subject: [PATCH 068/218] =?UTF-8?q?Config:=20Archival=20mode=20is=20no=20l?= =?UTF-8?q?onger=20automatically=20enabled=20when=20netAddress=20is=20s?= =?UTF-8?q?=E2=80=A6=20(#5904)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/algocfg/profileCommand.go | 4 +- config/config.go | 3 -- config/config_test.go | 47 +------------------ .../cli/goal/expect/catchpointCatchupTest.exp | 4 +- 4 files changed, 7 insertions(+), 51 deletions(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 06ae4ff61a..34f9183009 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -69,7 +69,9 @@ var ( relay = configUpdater{ description: "Relay consensus messages across the network and support catchup.", updateFunc: func(cfg config.Local) config.Local { - cfg.Archival = true + cfg.MaxBlockHistoryLookback = 20000 + cfg.CatchpointFileHistoryLength = 3 + cfg.CatchpointTracking = 2 cfg.EnableLedgerService = true cfg.EnableBlockService = true cfg.NetAddress = ":4160" diff --git a/config/config.go b/config/config.go index c5dc5d9541..a8beb05800 100644 --- a/config/config.go +++ b/config/config.go @@ -145,10 +145,7 @@ func mergeConfigFromFile(configpath string, source Local) (Local, error) { err = loadConfig(f, &source) - // For now, all relays (listening for incoming connections) are also Archival - // We can change this logic in the future, but it's currently the sanest default. if source.NetAddress != "" { - source.Archival = true source.EnableLedgerService = true source.EnableBlockService = true diff --git a/config/config_test.go b/config/config_test.go index 914e1e8e9a..432c0f9281 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -112,7 +112,8 @@ func TestLocal_MergeConfig(t *testing.T) { c2, err := mergeConfigFromDir(tempDir, defaultConfig) require.NoError(t, err) - require.Equal(t, defaultConfig.Archival || c1.NetAddress != "", c2.Archival) + require.Equal(t, defaultConfig.EnableLedgerService || c1.NetAddress != "", c2.EnableLedgerService) + require.Equal(t, defaultConfig.EnableBlockService || c1.NetAddress != "", c2.EnableBlockService) require.Equal(t, defaultConfig.IncomingConnectionsLimit, c2.IncomingConnectionsLimit) require.Equal(t, defaultConfig.BaseLoggerDebugLevel, c2.BaseLoggerDebugLevel) @@ -165,50 +166,6 @@ func TestLoadPhonebookMissing(t *testing.T) { require.True(t, os.IsNotExist(err)) } -func TestArchivalIfRelay(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - testArchivalIfRelay(t, true) -} - -func TestArchivalIfNotRelay(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - testArchivalIfRelay(t, false) -} - -func testArchivalIfRelay(t *testing.T, relay bool) { - tempDir := t.TempDir() - - c1 := struct { - NetAddress string - }{} - if relay { - c1.NetAddress = ":1234" - } - - // write our reduced version of the Local struct - fileToMerge := filepath.Join(tempDir, ConfigFilename) - f, err := os.OpenFile(fileToMerge, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err == nil { - enc := json.NewEncoder(f) - err = enc.Encode(c1) - f.Close() - } - require.NoError(t, err) - require.False(t, defaultConfig.Archival, "Default should be non-archival") - - c2, err := mergeConfigFromDir(tempDir, defaultConfig) - require.NoError(t, err) - if relay { - require.True(t, c2.Archival, "Relay should be archival") - } else { - require.False(t, c2.Archival, "Non-relay should still be non-archival") - } -} - func TestLocal_ConfigExampleIsCorrect(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp b/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp index 0a3b8ea48e..7ba924f99d 100644 --- a/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp +++ b/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp @@ -6,7 +6,7 @@ log_user 1 # # The goal of the test is to demonstrate the catchpoint catchup functionality using the goal command line interface. # It does that by deploying a single relay, which advances until it generates a catchpoint. -# Once it does, another node is started, and instructred to catchup using the catchpoint from the first relay. +# Once it does, another node is started, and instructed to catchup using the catchpoint from the first relay. # To make sure that the second node won't be using the "regular" catchup, we tunnel all the communication between the two using a proxy. # The proxy is responsible to filter out block number 2. This would prevent the "regular" catchup from working, # and would be a good test ground for the catchpoint catchup. @@ -64,7 +64,7 @@ if { [catch { ::AlgorandGoal::CreateNetwork $NETWORK_NAME $NETWORK_TEMPLATE $TEST_ALGO_DIR $TEST_ROOT_DIR # Update the Primary Node configuration - exec -- cat "$TEST_ROOT_DIR/Primary/config.json" | jq {. |= . + {"MaxAcctLookback": 2, "CatchpointInterval": 4,"EnableRequestLogger":true}} > $TEST_ROOT_DIR/Primary/config.json.new + exec -- cat "$TEST_ROOT_DIR/Primary/config.json" | jq {. |= . + {"MaxAcctLookback": 2, "CatchpointInterval": 4,"EnableRequestLogger":true,"Archival":true}} > $TEST_ROOT_DIR/Primary/config.json.new exec rm $TEST_ROOT_DIR/Primary/config.json exec mv $TEST_ROOT_DIR/Primary/config.json.new $TEST_ROOT_DIR/Primary/config.json From 135a1b4f0dd24c9fb9298b83170600e6657eee86 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 22 Jan 2024 13:43:42 -0500 Subject: [PATCH 069/218] Network: optimize cache mem and speed for messageFilter (#5913) --- network/messageFilter.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/network/messageFilter.go b/network/messageFilter.go index ed19f24b5c..b098eddf79 100644 --- a/network/messageFilter.go +++ b/network/messageFilter.go @@ -26,7 +26,7 @@ import ( // IncomingMessage represents a message arriving from some peer in our p2p network type messageFilter struct { deadlock.Mutex - buckets []map[crypto.Digest]bool + buckets []map[crypto.Digest]struct{} maxBucketSize int currentTopBucket int nonce [16]byte @@ -34,14 +34,12 @@ type messageFilter struct { func makeMessageFilter(bucketsCount, maxBucketSize int) *messageFilter { mf := &messageFilter{ - buckets: make([]map[crypto.Digest]bool, bucketsCount), + buckets: make([]map[crypto.Digest]struct{}, bucketsCount), maxBucketSize: maxBucketSize, currentTopBucket: 0, } - for i := range mf.buckets { - mf.buckets[i] = make(map[crypto.Digest]bool) - } crypto.RandBytes(mf.nonce[:]) + mf.buckets[mf.currentTopBucket] = make(map[crypto.Digest]struct{}, mf.maxBucketSize) return mf } @@ -69,19 +67,19 @@ func (f *messageFilter) CheckDigest(msgHash crypto.Digest, add bool, promote boo if !has { // we don't have this entry. add it. - f.buckets[f.currentTopBucket][msgHash] = true + f.buckets[f.currentTopBucket][msgHash] = struct{}{} } else { // we already have it. // do we need to promote it ? if promote && f.currentTopBucket != idx { delete(f.buckets[idx], msgHash) - f.buckets[f.currentTopBucket][msgHash] = true + f.buckets[f.currentTopBucket][msgHash] = struct{}{} } } // check to see if the current bucket reached capacity. if len(f.buckets[f.currentTopBucket]) >= f.maxBucketSize { f.currentTopBucket = (f.currentTopBucket + len(f.buckets) - 1) % len(f.buckets) - f.buckets[f.currentTopBucket] = make(map[crypto.Digest]bool) + f.buckets[f.currentTopBucket] = make(map[crypto.Digest]struct{}, f.maxBucketSize) } return has From 2e0644cd477f041dac7458e07f89dc37e626cb97 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:09:00 -0500 Subject: [PATCH 070/218] Tests: Adjust catchup e2e tests for non-archival relays (#5917) --- netdeploy/networkTemplate.go | 3 +++ .../catchup/catchpointCatchup_test.go | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go index ec3db61eb9..d8c30d66ef 100644 --- a/netdeploy/networkTemplate.go +++ b/netdeploy/networkTemplate.go @@ -321,6 +321,9 @@ func createConfigFile(node remote.NodeConfigGoal, configFile string, numNodes in if node.IsRelay { // Have relays listen on any localhost port cfg.NetAddress = "127.0.0.1:0" + + cfg.Archival = false // make it explicit non-archival + cfg.MaxBlockHistoryLookback = 20000 // to save blocks beyond MaxTxnLife=13 } else { // Non-relays should not open incoming connections cfg.IncomingConnectionsLimit = 0 diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index 6bc07e6cac..7d731e9e94 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -126,6 +126,10 @@ func configureCatchpointGeneration(a *require.Assertions, nodeController *nodeco a.NoError(err) cfg.CatchpointInterval = basicTestCatchpointInterval + cfg.Archival = false // make it explicit non-archival + cfg.MaxBlockHistoryLookback = 20000 // to save blocks beyond MaxTxnLife=13 + cfg.CatchpointTracking = 2 // to enable catchpoints on non-archival nodes + cfg.CatchpointFileHistoryLength = 30 // to store more than 2 default catchpoints cfg.MaxAcctLookback = 2 err = cfg.SaveToDisk(nodeController.GetDataDir()) a.NoError(err) @@ -383,7 +387,6 @@ func TestCatchpointLabelGeneration(t *testing.T) { expectLabels bool }{ {4, true, true}, - {4, false, true}, {0, true, false}, } @@ -506,8 +509,11 @@ func TestNodeTxHandlerRestart(t *testing.T) { a.NoError(err) const catchpointInterval = 16 cfg.CatchpointInterval = catchpointInterval - cfg.CatchpointTracking = 2 - cfg.TxSyncIntervalSeconds = 200000 // disable txSync + cfg.Archival = false // make it explicit non-archival + cfg.MaxBlockHistoryLookback = 20000 // to save blocks beyond MaxTxnLife=13 + cfg.CatchpointTracking = 2 // to enable catchpoints on non-archival nodes + cfg.CatchpointFileHistoryLength = 30 // to store more than 2 default catchpoints + cfg.TxSyncIntervalSeconds = 200000 // disable txSync cfg.SaveToDisk(relayNode.GetDataDir()) fixture.Start() @@ -612,8 +618,11 @@ func TestReadyEndpoint(t *testing.T) { a.NoError(err) const catchpointInterval = 16 cfg.CatchpointInterval = catchpointInterval - cfg.CatchpointTracking = 2 - cfg.TxSyncIntervalSeconds = 200000 // disable txSync + cfg.Archival = false // make it explicit non-archival + cfg.MaxBlockHistoryLookback = 20000 // to save blocks beyond MaxTxnLife=13 + cfg.CatchpointTracking = 2 // to enable catchpoints on non-archival nodes + cfg.CatchpointFileHistoryLength = 30 // to store more than 2 default catchpoints + cfg.TxSyncIntervalSeconds = 200000 // disable txSync cfg.SaveToDisk(relayNode.GetDataDir()) fixture.Start() From a12324fd989883ce9bdc4c4fdecd26d7d59db965 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Tue, 23 Jan 2024 13:10:07 -0500 Subject: [PATCH 071/218] Config: Set relay profile's MaxBalanceHistoryLookback to 22000 (#5918) --- cmd/algocfg/profileCommand.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 34f9183009..4cd9bf5c9e 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -69,7 +69,7 @@ var ( relay = configUpdater{ description: "Relay consensus messages across the network and support catchup.", updateFunc: func(cfg config.Local) config.Local { - cfg.MaxBlockHistoryLookback = 20000 + cfg.MaxBlockHistoryLookback = 22000 // Enough to support 2 catchpoints with some wiggle room for nodes to catch up from the older one cfg.CatchpointFileHistoryLength = 3 cfg.CatchpointTracking = 2 cfg.EnableLedgerService = true From 349073102f9f57fa6a267f9f76d20f085b0ff43b Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:28:49 -0500 Subject: [PATCH 072/218] blockdb: bound max deleted blocks per blockdb sync (#5910) --- ledger/blockqueue.go | 17 ++++++ ledger/blockqueue_test.go | 115 ++++++++++++++++++++++++++++++++++++++ ledger/ledger.go | 6 +- ledger/ledger_test.go | 2 + 4 files changed, 137 insertions(+), 3 deletions(-) diff --git a/ledger/blockqueue.go b/ledger/blockqueue.go index 686d51b96f..7c17281017 100644 --- a/ledger/blockqueue.go +++ b/ledger/blockqueue.go @@ -111,6 +111,8 @@ func (bq *blockQueue) stop() { } } +const maxDeletionBatchSize = 10_000 + func (bq *blockQueue) syncer() { bq.mu.Lock() for { @@ -164,6 +166,21 @@ func (bq *blockQueue) syncer() { bq.mu.Unlock() minToSave := bq.l.notifyCommit(committed) + var earliest basics.Round + err = bq.l.blockDBs.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + var err0 error + earliest, err0 = blockdb.BlockEarliest(tx) + if err0 != nil { + bq.l.log.Warnf("blockQueue.syncer: BlockEarliest(): %v", err0) + } + return err0 + }) + if err == nil { + if basics.SubSaturate(minToSave, earliest) > maxDeletionBatchSize { + minToSave = basics.AddSaturate(earliest, maxDeletionBatchSize) + } + } + bfstart := time.Now() ledgerSyncBlockforgetCount.Inc(nil) err = bq.l.blockDBs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { diff --git a/ledger/blockqueue_test.go b/ledger/blockqueue_test.go index 8b451a95ed..e74fbc0b3b 100644 --- a/ledger/blockqueue_test.go +++ b/ledger/blockqueue_test.go @@ -17,9 +17,12 @@ package ledger import ( + "context" + "database/sql" "errors" "fmt" "testing" + "time" "github.com/stretchr/testify/require" @@ -29,10 +32,12 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/ledger/store/blockdb" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util/db" ) func randomBlock(r basics.Round) blockEntry { @@ -128,3 +133,113 @@ func TestGetEncodedBlockCert(t *testing.T) { expectedErr := &ledgercore.ErrNoEntry{} require.True(t, errors.As(err, expectedErr)) } + +// it is not great to use trackers here but at the moment there is no abstraction for the ledger +type uptoTracker struct { + emptyTracker +} + +// committedUpTo in the emptyTracker just stores the committed round. +func (t *uptoTracker) committedUpTo(committedRnd basics.Round) (minRound, lookback basics.Round) { + return 5_000, basics.Round(0) +} + +// TestBlockQueueSyncerDeletion ensures that the block queue syncer deletes no more than maxDeletionBatchSize blocks at time +func TestBlockQueueSyncerDeletion(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tests := []struct { + name string + expectedEarliest basics.Round + tracker ledgerTracker + }{ + {"max_batch", maxDeletionBatchSize, nil}, // no trackers, max deletion + {"5k_tracker", 5_000, &uptoTracker{}}, // tracker sets minToSave to 5k + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + const dbMem = true + blockDBs, err := db.OpenPair(t.Name()+".block.sqlite", dbMem) + require.NoError(t, err) + + log := logging.TestingLog(t) + err = blockDBs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return initBlocksDB(tx, log, []bookkeeping.Block{}, false) + }) + require.NoError(t, err) + + // add 15k blocks + const maxBlocks = maxDeletionBatchSize + maxDeletionBatchSize/2 // 15_000 + err = blockDBs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + for i := 0; i < maxBlocks; i++ { + err0 := blockdb.BlockPut( + tx, + bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: basics.Round(i)}}, + agreement.Certificate{}) + if err0 != nil { + return err0 + } + } + return nil + }) + require.NoError(t, err) + + var earliest, latest basics.Round + err = blockDBs.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + var err0 error + earliest, err0 = blockdb.BlockEarliest(tx) + if err0 != nil { + return err0 + } + latest, err0 = blockdb.BlockLatest(tx) + return err0 + }) + require.NoError(t, err) + require.Equal(t, basics.Round(0), earliest) + require.Equal(t, basics.Round(maxBlocks-1), latest) + + // trigger deletion and ensure no more than 10k blocks gone + //make a minimal ledger for blockqueue + + l := &Ledger{ + log: log, + blockDBs: blockDBs, + } + if test.tracker != nil { + l.trackers.trackers = append(l.trackers.trackers, test.tracker) + } + blockq, _ := newBlockQueue(l) + err = blockq.start() + require.NoError(t, err) + + // add a block. Eventually the syncer will called on an empty ledger + // forcing deleting all 15_000 rounds. The deletion scoping should limit it to 10_000 rounds instead + err = blockq.putBlock(bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: maxBlocks}}, agreement.Certificate{}) + require.NoError(t, err) + + require.Eventually(t, func() bool { + var latest basics.Round + err = blockDBs.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + var err0 error + latest, err0 = blockdb.BlockLatest(tx) + return err0 + }) + require.NoError(t, err) + return latest == maxBlocks + }, 1*time.Second, 10*time.Millisecond) + + blockq.stop() + + err = blockDBs.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + var err0 error + earliest, err0 = blockdb.BlockEarliest(tx) + return err0 + }) + require.NoError(t, err) + require.Equal(t, test.expectedEarliest, earliest) + }) + } +} diff --git a/ledger/ledger.go b/ledger/ledger.go index d5436ad5d4..fa1be1a766 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -173,7 +173,7 @@ func OpenLedger[T string | DirsAndPrefix]( start := time.Now() ledgerInitblocksdbCount.Inc(nil) err = l.blockDBs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return initBlocksDB(tx, l, []bookkeeping.Block{genesisInitState.Block}, cfg.Archival) + return initBlocksDB(tx, l.log, []bookkeeping.Block{genesisInitState.Block}, cfg.Archival) }) ledgerInitblocksdbMicros.AddMicrosecondsSince(start, nil) if err != nil { @@ -364,7 +364,7 @@ func (l *Ledger) setSynchronousMode(ctx context.Context, synchronousMode db.Sync // initBlocksDB performs DB initialization: // - creates and populates it with genesis blocks // - ensures DB is in good shape for archival mode and resets it if not -func initBlocksDB(tx *sql.Tx, l *Ledger, initBlocks []bookkeeping.Block, isArchival bool) (err error) { +func initBlocksDB(tx *sql.Tx, log logging.Logger, initBlocks []bookkeeping.Block, isArchival bool) (err error) { err = blockdb.BlockInit(tx, initBlocks) if err != nil { err = fmt.Errorf("initBlocksDB.blockInit %v", err) @@ -382,7 +382,7 @@ func initBlocksDB(tx *sql.Tx, l *Ledger, initBlocks []bookkeeping.Block, isArchi // Detect possible problem - archival node needs all block but have only subsequence of them // So reset the DB and init it again if earliest != basics.Round(0) { - l.log.Warnf("resetting blocks DB (earliest block is %v)", earliest) + log.Warnf("resetting blocks DB (earliest block is %v)", earliest) err := blockdb.BlockResetDB(tx) if err != nil { err = fmt.Errorf("initBlocksDB.blockResetDB %v", err) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 79ae16284d..ab5bc293a3 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -2877,11 +2877,13 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi } triggerDeleteVoters(t, l, genesisInitState) + l.acctsOnline.voters.votersMu.Lock() vtSnapshot := l.acctsOnline.voters.votersForRoundCache // verifying that the tree for round 512 is still in the cache, but the tree for round 256 is evicted. require.Contains(t, vtSnapshot, basics.Round(496)) require.NotContains(t, vtSnapshot, basics.Round(240)) + l.acctsOnline.voters.votersMu.Unlock() err = l.reloadLedger() require.NoError(t, err) From ce9177226ffe12a369773194ade5a95ea33f216e Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Tue, 23 Jan 2024 20:31:43 +0000 Subject: [PATCH 073/218] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +0 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885 From 2e41eef1ae64a283f90b2fe54e07df90b47cc5a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:06:19 -0500 Subject: [PATCH 074/218] Build: bump jinja2 from 3.1.2 to 3.1.3 in /test/heapwatch (#5903) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/heapwatch/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt index b46aead08e..d8ee41d7e6 100644 --- a/test/heapwatch/requirements.txt +++ b/test/heapwatch/requirements.txt @@ -1,6 +1,6 @@ dash==2.11.1 dash-table==5.0.0 -Jinja2==3.1.2 +Jinja2==3.1.3 matplotlib==3.7.2 plotly==5.16.0 py-algorand-sdk==2.3.0 From 86319605bebbc2d48f68b6d4360ebc917e018e85 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:26:03 -0500 Subject: [PATCH 075/218] Archival Support: Remove configurable support for catching up from "archivers" (#5920) Co-authored-by: John Jannotti --- catchup/catchpointService.go | 19 +--- catchup/peerSelector_test.go | 69 ++++++------ catchup/service.go | 80 ++++--------- catchup/service_test.go | 104 ++++++----------- config/localTemplate.go | 20 +--- config/local_defaults.go | 2 - installer/config.json.example | 2 - network/wsNetwork.go | 15 ++- network/wsNetwork_test.go | 132 ++++++++++------------ rpcs/blockService.go | 32 +----- rpcs/blockService_test.go | 156 ++++++++++---------------- test/testdata/configs/config-v33.json | 2 - 12 files changed, 227 insertions(+), 406 deletions(-) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index f71a4209ae..2c4f6dfc41 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -806,20 +806,11 @@ func (cs *CatchpointCatchupService) initDownloadPeerSelector() { } func (cs *CatchpointCatchupService) makeCatchpointPeerSelector() *peerSelector { - if cs.config.EnableCatchupFromArchiveServers { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - }) - } else { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, - }) - } + return makePeerSelector( + cs.net, + []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, + }) } // checkLedgerDownload sends a HEAD request to the ledger endpoint of peers to validate the catchpoint's availability diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index 2a907934a8..aa8d348d43 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -134,7 +134,7 @@ func TestPeerSelector_RankPeer(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return peers - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, ) psp, err := peerSelector.getNextPeer() @@ -194,14 +194,14 @@ func TestPeerSelector_PeerDownloadRanking(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) archivalPeer, err := peerSelector.getNextPeer() @@ -243,7 +243,7 @@ func TestPeerSelector_FindMissingPeer(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return []network.Peer{} - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, ) poolIdx, peerIdx := peerSelector.findPeer(&peerSelectorPeer{mockHTTPPeer{address: "abcd"}, 0}) @@ -261,14 +261,14 @@ func TestPeerSelector_HistoricData(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -335,14 +335,14 @@ func TestPeerSelector_PeersDownloadFailed(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -411,14 +411,14 @@ func TestPeerSelector_Penalty(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -468,44 +468,39 @@ func TestPeerSelector_PeerDownloadDurationToRank(t *testing.T) { peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} peers3 := []network.Peer{&mockHTTPPeer{address: "c1"}, &mockHTTPPeer{address: "c2"}} peers4 := []network.Peer{&mockHTTPPeer{address: "d1"}, &mockHTTPPeer{address: "b2"}} - peers5 := []network.Peer{&mockHTTPPeer{address: "e1"}, &mockHTTPPeer{address: "b2"}} peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookRelays { peers = append(peers, peers1...) - } else if opt == network.PeersPhonebookRelays { - peers = append(peers, peers2...) } else if opt == network.PeersConnectedOut { - peers = append(peers, peers3...) + peers = append(peers, peers2...) } else if opt == network.PeersPhonebookArchivalNodes { - peers = append(peers, peers4...) + peers = append(peers, peers3...) } else { // PeersConnectedIn - peers = append(peers, peers5...) + peers = append(peers, peers4...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}}, + }), []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}}, ) _, err := peerSelector.getNextPeer() require.NoError(t, err) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank0LowBlockTime, peerRank0HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers1[0], network.PeersPhonebookArchivers}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers1[0], network.PeersPhonebookRelays}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank1LowBlockTime, peerRank1HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers2[0], network.PeersPhonebookRelays}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers2[0], network.PeersConnectedOut}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank2LowBlockTime, peerRank2HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers3[0], network.PeersConnectedOut}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers3[0], network.PeersPhonebookArchivalNodes}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank3LowBlockTime, peerRank3HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers4[0], network.PeersPhonebookArchivalNodes}, 500*time.Millisecond)) - require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank4LowBlockTime, peerRank4HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers5[0], network.PeersConnectedIn}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers4[0], network.PeersConnectedIn}, 500*time.Millisecond)) } @@ -513,7 +508,7 @@ func TestPeerSelector_LowerUpperBounds(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, @@ -536,7 +531,7 @@ func TestPeerSelector_FullResetRequestPenalty(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivers} + class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivalNodes} hs := makeHistoricStatus(10, class) hs.push(5, 1, class) require.Equal(t, 1, len(hs.requestGaps)) @@ -551,7 +546,7 @@ func TestPeerSelector_PenaltyBounds(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers} + class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes} hs := makeHistoricStatus(peerHistoryWindowSize, class) for x := 0; x < 65; x++ { r0 := hs.push(peerRank2LowBlockTime+50, uint64(x+1), class) @@ -578,11 +573,11 @@ func TestPeerSelector_ClassUpperBound(t *testing.T) { t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} - pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} + pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } } @@ -613,11 +608,11 @@ func TestPeerSelector_ClassLowerBound(t *testing.T) { t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} - pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} + pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } } @@ -647,14 +642,14 @@ func TestPeerSelector_EvictionAndUpgrade(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) diff --git a/catchup/service.go b/catchup/service.go index 1093f851a2..58fc3ae6b8 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -868,72 +868,34 @@ func (s *Service) roundIsNotSupported(nextRound basics.Round) bool { func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch bool) *peerSelector { var peerClasses []peerClass - if cfg.EnableCatchupFromArchiveServers { - if pipelineFetch { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } + if pipelineFetch { + if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, } } else { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersPhonebookArchivers}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivers}, - } + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, } } } else { - if pipelineFetch { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } + if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, } } else { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, } } } diff --git a/catchup/service_test.go b/catchup/service_test.go index c5b8cfccab..8deb692b0d 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -954,71 +954,18 @@ func TestCatchupUnmatchedCertificate(t *testing.T) { } } -// TestCreatePeerSelector tests if the correct peer selector coonfigurations are prepared +// TestCreatePeerSelector tests if the correct peer selector configurations are prepared func TestCreatePeerSelector(t *testing.T) { partitiontest.PartitionTest(t) // Make Service cfg := defaultConfig - cfg.EnableCatchupFromArchiveServers = true - + // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = true cfg.NetAddress = "someAddress" + cfg.EnableGossipService = true s := MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps := createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 5, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - require.Equal(t, peerRankInitialFifthPriority, ps.peerClasses[4].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[4].peerClass) - - // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress == ""; pipelineFetch = true; - cfg.EnableCatchupFromArchiveServers = true - cfg.NetAddress = "" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 4, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - - // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress != ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = true - cfg.NetAddress = "someAddress" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 5, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - require.Equal(t, peerRankInitialFifthPriority, ps.peerClasses[4].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[4].peerClass) - - // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress == ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = true - cfg.NetAddress = "" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) require.Equal(t, 4, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) @@ -1029,28 +976,26 @@ func TestCreatePeerSelector(t *testing.T) { require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[3].peerClass) + require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = true - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "someAddress" + // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = true + cfg.NetAddress = "" + cfg.EnableGossipService = true s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 4, len(ps.peerClasses)) + require.Equal(t, 3, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) + require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = true - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "" + // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = true + cfg.NetAddress = "someAddress" + cfg.EnableGossipService = false s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, true) @@ -1063,9 +1008,9 @@ func TestCreatePeerSelector(t *testing.T) { require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = false + // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = false cfg.NetAddress = "someAddress" + cfg.EnableGossipService = true s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) @@ -1080,9 +1025,24 @@ func TestCreatePeerSelector(t *testing.T) { require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = false + // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = false cfg.NetAddress = "" + cfg.EnableGossipService = true + s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) + ps = createPeerSelector(s.net, s.cfg, false) + + require.Equal(t, 3, len(ps.peerClasses)) + require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) + require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) + require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) + + require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + + // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = false + cfg.NetAddress = "someAddress" + cfg.EnableGossipService = false s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) diff --git a/config/localTemplate.go b/config/localTemplate.go index c6e1ba0a2c..618facfd29 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -44,13 +44,13 @@ type Local struct { // for an existing parameter. This field tag must be updated any time we add a new version. Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33"` - // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. Relays (nodes with a valid NetAddress) are always Archival, regardless of this setting. This may change in the future. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only effects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. + // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only affects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. Archival bool `version[0]:"false"` // GossipFanout sets the maximum number of peers the node will connect to with outgoing connections. If the list of peers is less than this setting, fewer connections will be made. The node will not connect to the same peer multiple times (with outgoing connections). GossipFanout int `version[0]:"4"` - // NetAddress is the address and/or port on which the relay node listens for incoming connections, or blank to ignore incoming connections. Specify an IP and port or just a port. For example, 127.0.0.1:0 will listen on a random port on the localhost. + // NetAddress is the address and/or port on which a node listens for incoming connections, or blank to ignore incoming connections. Specify an IP and port or just a port. For example, 127.0.0.1:0 will listen on a random port on the localhost. NetAddress string `version[0]:""` // ReconnectTime is deprecated and unused. @@ -454,28 +454,16 @@ type Local struct { // VerifiedTranscationsCacheSize defines the number of transactions that the verified transactions cache would hold before cycling the cache storage in a round-robin fashion. VerifiedTranscationsCacheSize int `version[14]:"30000" version[23]:"150000"` - // EnableCatchupFromArchiveServers controls which peers the catchup service would use in order to catchup. - // When enabled, the catchup service would use the archive servers before falling back to the relays. - // On networks that don't have archive servers, this becomes a no-op, as the catchup service would have no - // archive server to pick from, and therefore automatically selects one of the relay nodes. - EnableCatchupFromArchiveServers bool `version[15]:"false"` - // DisableLocalhostConnectionRateLimit controls whether the incoming connection rate limit would apply for // connections that are originating from the local machine. Setting this to "true", allow to create large // local-machine networks that won't trip the incoming connection limit observed by relays. DisableLocalhostConnectionRateLimit bool `version[16]:"true"` // BlockServiceCustomFallbackEndpoints is a comma delimited list of endpoints which the block service uses to - // redirect the http requests to in case it does not have the round. If it is not specified, will check - // EnableBlockServiceFallbackToArchiver. + // redirect the http requests to in case it does not have the round. If empty, the block service will return + // StatusNotFound (404) BlockServiceCustomFallbackEndpoints string `version[16]:""` - // EnableBlockServiceFallbackToArchiver controls whether the block service redirects the http requests to - // an archiver or return StatusNotFound (404) when in does not have the requested round, and - // BlockServiceCustomFallbackEndpoints is empty. - // The archiver is randomly selected, if none is available, will return StatusNotFound (404). - EnableBlockServiceFallbackToArchiver bool `version[16]:"true" version[31]:"false"` - // CatchupBlockValidateMode is a development and testing configuration used by the catchup service. // It can be used to omit certain validations to speed up the catchup process, or to apply extra validations which are redundant in normal operation. // This field is a bit-field with: diff --git a/config/local_defaults.go b/config/local_defaults.go index 791e6e2225..d2a73d4c6f 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -64,8 +64,6 @@ var defaultLocal = Local{ EnableAgreementTimeMetrics: false, EnableAssembleStats: false, EnableBlockService: false, - EnableBlockServiceFallbackToArchiver: false, - EnableCatchupFromArchiveServers: false, EnableDeveloperAPI: false, EnableExperimentalAPI: false, EnableFollowMode: false, diff --git a/installer/config.json.example b/installer/config.json.example index aa1cb71712..d9188ef748 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -43,8 +43,6 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, - "EnableBlockServiceFallbackToArchiver": false, - "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 9ce5f331ae..5bfabbaf2b 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -1895,15 +1895,14 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses [] } relaysAddresses = nil } - if wn.config.EnableCatchupFromArchiveServers || wn.config.EnableBlockServiceFallbackToArchiver { - archiverAddresses, err = wn.resolveSRVRecords("archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) - if err != nil { - // only log this warning on testnet or devnet - if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { - wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) - } - archiverAddresses = nil + + archiverAddresses, err = wn.resolveSRVRecords("archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + if err != nil { + // only log this warning on testnet or devnet + if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { + wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) } + archiverAddresses = nil } return } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index b7cd873bcf..e35cc7d177 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -4127,95 +4127,83 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { var netA *WebsocketNetwork var refreshRelayDNSBootstrapID = ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)" - testRefreshWithConfig := func(refreshTestConf config.Local) { - rapid.Check(t, func(t1 *rapid.T) { - refreshTestConf.DNSBootstrapID = refreshRelayDNSBootstrapID - netA = makeTestWebsocketNodeWithConfig(t, refreshTestConf) - netA.NetworkID = nonHardcodedNetworkIDGen().Draw(t1, "network") - - primarySRVBootstrap := strings.Replace(".algorand.network", "", string(netA.NetworkID), -1) - backupSRVBootstrap := strings.Replace(".algorand.net", "", string(netA.NetworkID), -1) - var primaryRelayResolvedRecords []string - var secondaryRelayResolvedRecords []string - var primaryArchiveResolvedRecords []string - var secondaryArchiveResolvedRecords []string - - for _, record := range []string{"r1.algorand-.network", - "r2.algorand-.network", "r3.algorand-.network"} { - var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) - primaryRelayResolvedRecords = append(primaryRelayResolvedRecords, recordSub) - secondaryRelayResolvedRecords = append(secondaryRelayResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) - } - - for _, record := range []string{"r1archive.algorand-.network", - "r2archive.algorand-.network", "r3archive.algorand-.network"} { - var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) - primaryArchiveResolvedRecords = append(primaryArchiveResolvedRecords, recordSub) - secondaryArchiveResolvedRecords = append(secondaryArchiveResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) - } + refreshTestConf := defaultConfig - // Mock the SRV record lookup - netA.resolveSRVRecords = func(service string, protocol string, name string, fallbackDNSResolverAddress string, - secure bool) (addrs []string, err error) { - if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { - return primaryRelayResolvedRecords, nil - } else if service == "algobootstrap" && protocol == "tcp" && name == backupSRVBootstrap { - return secondaryRelayResolvedRecords, nil - } + rapid.Check(t, func(t1 *rapid.T) { + refreshTestConf.DNSBootstrapID = refreshRelayDNSBootstrapID + netA = makeTestWebsocketNodeWithConfig(t, refreshTestConf) + netA.NetworkID = nonHardcodedNetworkIDGen().Draw(t1, "network") + + primarySRVBootstrap := strings.Replace(".algorand.network", "", string(netA.NetworkID), -1) + backupSRVBootstrap := strings.Replace(".algorand.net", "", string(netA.NetworkID), -1) + var primaryRelayResolvedRecords []string + var secondaryRelayResolvedRecords []string + var primaryArchiveResolvedRecords []string + var secondaryArchiveResolvedRecords []string + + for _, record := range []string{"r1.algorand-.network", + "r2.algorand-.network", "r3.algorand-.network"} { + var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) + primaryRelayResolvedRecords = append(primaryRelayResolvedRecords, recordSub) + secondaryRelayResolvedRecords = append(secondaryRelayResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) + } - if service == "archive" && protocol == "tcp" && name == primarySRVBootstrap { - return primaryArchiveResolvedRecords, nil - } else if service == "archive" && protocol == "tcp" && name == backupSRVBootstrap { - return secondaryArchiveResolvedRecords, nil - } + for _, record := range []string{"r1archive.algorand-.network", + "r2archive.algorand-.network", "r3archive.algorand-.network"} { + var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) + primaryArchiveResolvedRecords = append(primaryArchiveResolvedRecords, recordSub) + secondaryArchiveResolvedRecords = append(secondaryArchiveResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) + } - return + // Mock the SRV record lookup + netA.resolveSRVRecords = func(service string, protocol string, name string, fallbackDNSResolverAddress string, + secure bool) (addrs []string, err error) { + if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { + return primaryRelayResolvedRecords, nil + } else if service == "algobootstrap" && protocol == "tcp" && name == backupSRVBootstrap { + return secondaryRelayResolvedRecords, nil } - relayPeers := netA.GetPeers(PeersPhonebookRelays) - assert.Equal(t, 0, len(relayPeers)) - - archivePeers := netA.GetPeers(PeersPhonebookArchivers) - assert.Equal(t, 0, len(archivePeers)) - - netA.refreshRelayArchivePhonebookAddresses() + if service == "archive" && protocol == "tcp" && name == primarySRVBootstrap { + return primaryArchiveResolvedRecords, nil + } else if service == "archive" && protocol == "tcp" && name == backupSRVBootstrap { + return secondaryArchiveResolvedRecords, nil + } - relayPeers = netA.GetPeers(PeersPhonebookRelays) + return + } - assert.Equal(t, 3, len(relayPeers)) - relayAddrs := make([]string, 0, len(relayPeers)) - for _, peer := range relayPeers { - relayAddrs = append(relayAddrs, peer.(HTTPPeer).GetAddress()) - } + relayPeers := netA.GetPeers(PeersPhonebookRelays) + assert.Equal(t, 0, len(relayPeers)) - assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) + archivePeers := netA.GetPeers(PeersPhonebookArchivers) + assert.Equal(t, 0, len(archivePeers)) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + netA.refreshRelayArchivePhonebookAddresses() - if refreshTestConf.EnableBlockServiceFallbackToArchiver { - // For the time being, we do not dedup resolved archive nodes - assert.Equal(t, len(primaryArchiveResolvedRecords)+len(secondaryArchiveResolvedRecords), len(archivePeers)) + relayPeers = netA.GetPeers(PeersPhonebookRelays) - archiveAddrs := make([]string, 0, len(archivePeers)) - for _, peer := range archivePeers { - archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) - } + assert.Equal(t, 3, len(relayPeers)) + relayAddrs := make([]string, 0, len(relayPeers)) + for _, peer := range relayPeers { + relayAddrs = append(relayAddrs, peer.(HTTPPeer).GetAddress()) + } - assert.ElementsMatch(t, append(primaryArchiveResolvedRecords, secondaryArchiveResolvedRecords...), archiveAddrs) + assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) - } else { - assert.Equal(t, 0, len(archivePeers)) - } + archivePeers = netA.GetPeers(PeersPhonebookArchivers) - }) - } + // TODO: For the time being, we do not dedup resolved archive nodes + assert.Equal(t, len(primaryArchiveResolvedRecords)+len(secondaryArchiveResolvedRecords), len(archivePeers)) - testRefreshWithConfig(defaultConfig) + archiveAddrs := make([]string, 0, len(archivePeers)) + for _, peer := range archivePeers { + archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) + } - configWithBlockServiceFallbackToArchiverEnabled := config.GetDefaultLocal() - configWithBlockServiceFallbackToArchiverEnabled.EnableBlockServiceFallbackToArchiver = true + assert.ElementsMatch(t, append(primaryArchiveResolvedRecords, secondaryArchiveResolvedRecords...), archiveAddrs) - testRefreshWithConfig(configWithBlockServiceFallbackToArchiverEnabled) + }) } /* diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 7245eb1881..8231b5a98b 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -36,7 +36,6 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -103,7 +102,6 @@ type BlockService struct { enableService bool enableServiceOverGossip bool fallbackEndpoints fallbackEndpoints - enableArchiverFallback bool log logging.Logger closeWaitGroup sync.WaitGroup mu deadlock.Mutex @@ -144,7 +142,6 @@ func MakeBlockService(log logging.Logger, config config.Local, ledger LedgerForB enableService: config.EnableBlockService, enableServiceOverGossip: config.EnableGossipBlockService, fallbackEndpoints: makeFallbackEndpoints(log, config.BlockServiceCustomFallbackEndpoints), - enableArchiverFallback: config.EnableBlockServiceFallbackToArchiver, log: log, memoryCap: config.BlockServiceMemCap, } @@ -384,13 +381,10 @@ func (bs *BlockService) handleCatchupReq(ctx context.Context, reqMsg network.Inc return } -// redirectRequest redirects the request to the next round robin fallback endpoing if available, otherwise, -// if EnableBlockServiceFallbackToArchiver is enabled, redirects to a random archiver. +// redirectRequest redirects the request to the next round robin fallback endpoint if available func (bs *BlockService) redirectRequest(round uint64, response http.ResponseWriter, request *http.Request) (ok bool) { peerAddress := bs.getNextCustomFallbackEndpoint() - if peerAddress == "" && bs.enableArchiverFallback { - peerAddress = bs.getRandomArchiver() - } + if peerAddress == "" { return false } @@ -411,30 +405,14 @@ func (bs *BlockService) getNextCustomFallbackEndpoint() (endpointAddress string) if len(bs.fallbackEndpoints.endpoints) == 0 { return } + + bs.mu.Lock() + defer bs.mu.Unlock() endpointAddress = bs.fallbackEndpoints.endpoints[bs.fallbackEndpoints.lastUsed] bs.fallbackEndpoints.lastUsed = (bs.fallbackEndpoints.lastUsed + 1) % len(bs.fallbackEndpoints.endpoints) return } -// getRandomArchiver returns a random archiver address -func (bs *BlockService) getRandomArchiver() (endpointAddress string) { - peers := bs.net.GetPeers(network.PeersPhonebookArchivers) - httpPeers := make([]network.HTTPPeer, 0, len(peers)) - - for _, peer := range peers { - httpPeer, validHTTPPeer := peer.(network.HTTPPeer) - if validHTTPPeer { - httpPeers = append(httpPeers, httpPeer) - } - } - if len(httpPeers) == 0 { - return - } - randIndex := crypto.RandUint64() % uint64(len(httpPeers)) - endpointAddress = httpPeers[randIndex].GetAddress() - return -} - // rawBlockBytes returns the block/cert for a given round, while taking the lock // to ensure the block service is currently active. func (bs *BlockService) rawBlockBytes(round basics.Round) ([]byte, error) { diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index e77fc7aa0e..3aab7c4abb 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -125,8 +125,9 @@ func TestHandleCatchupReqNegative(t *testing.T) { require.Equal(t, roundNumberParseErrMsg, string(val)) } -// TestRedirectFallbackArchiver tests the case when the block service fallback to another in the absence of a given block. -func TestRedirectFallbackArchiver(t *testing.T) { +// TestRedirectFallbackEndpoints tests the case when the block service falls back to another from +// BlockServiceCustomFallbackEndpoints in the absence of a given block. +func TestRedirectFallbackEndpoints(t *testing.T) { partitiontest.PartitionTest(t) log := logging.TestingLog(t) @@ -142,25 +143,23 @@ func TestRedirectFallbackArchiver(t *testing.T) { net1 := &httpTestPeerSource{} 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") - nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeA.start() defer nodeA.stop() - - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) nodeB.start() defer nodeB.stop() - net1.addPeer(nodeB.rootURL()) + config := config.GetDefaultLocal() + // Set the first to a bad address, the second to self, and the third to the one that has the block. + // If RR is right, should succeed. + config.BlockServiceCustomFallbackEndpoints = fmt.Sprintf("://badaddress,%s,%s", nodeA.rootURL(), nodeB.rootURL()) + + bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID") + bs2 := MakeBlockService(log, config, ledger2, net2, "test-genesis-ID") + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -235,60 +234,8 @@ func TestBlockServiceShutdown(t *testing.T) { <-requestDone } -// TestRedirectBasic tests the case when the block service redirects the request to elsewhere -func TestRedirectFallbackEndpoints(t *testing.T) { - partitiontest.PartitionTest(t) - - log := logging.TestingLog(t) - - ledger1 := makeLedger(t, "l1") - defer ledger1.Close() - ledger2 := makeLedger(t, "l2") - defer ledger2.Close() - addBlock(t, ledger2) - - net1 := &httpTestPeerSource{} - net2 := &httpTestPeerSource{} - - nodeA := &basicRPCNode{} - nodeB := &basicRPCNode{} - nodeA.start() - defer nodeA.stop() - nodeB.start() - defer nodeB.stop() - - config := config.GetDefaultLocal() - // Set the first to a bad address, the second to self, and the third to the one that has the block. - // If RR is right, should succeed. - config.BlockServiceCustomFallbackEndpoints = fmt.Sprintf("://badaddress,%s,%s", nodeA.rootURL(), nodeB.rootURL()) - bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}") - bs2 := MakeBlockService(log, config, ledger2, net2, "{genesisID}") - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) - require.NoError(t, err) - - client := http.Client{} - - ctx := context.Background() - parsedURL.Path = FormatBlockQuery(uint64(1), parsedURL.Path, net1) - blockURL := parsedURL.String() - request, err := http.NewRequest("GET", blockURL, nil) - require.NoError(t, err) - requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(config.CatchupHTTPBlockFetchTimeoutSec)*time.Second) - defer requestCancel() - request = request.WithContext(requestCtx) - network.SetUserAgentHeader(request.Header) - response, err := client.Do(request) - require.NoError(t, err) - - require.Equal(t, http.StatusOK, response.StatusCode) -} - -// TestRedirectFallbackArchiver tests the case when the block service -// fallback to another because its memory use it at capacity +// TestRedirectOnFullCapacity tests the case when the block service +// fallback to another because its memory use is at capacity func TestRedirectOnFullCapacity(t *testing.T) { partitiontest.PartitionTest(t) @@ -313,27 +260,31 @@ func TestRedirectOnFullCapacity(t *testing.T) { net1 := &httpTestPeerSource{} 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 - bs1.memoryCap = 250 - bs2.memoryCap = 250 - nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeA.start() defer nodeA.stop() - - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) nodeB.start() defer nodeB.stop() - net1.addPeer(nodeB.rootURL()) + configWithRedirects := config.GetDefaultLocal() + + configWithRedirects.BlockServiceCustomFallbackEndpoints = nodeB.rootURL() + + bs1 := MakeBlockService(log1, configWithRedirects, ledger1, net1, "test-genesis-ID") + + // config with no redirects + configNoRedirects := config.GetDefaultLocal() + configNoRedirects.BlockServiceCustomFallbackEndpoints = "" + + bs2 := MakeBlockService(log2, configNoRedirects, ledger2, net2, "test-genesis-ID") + // set the memory cap so that it can serve only 1 block at a time + bs1.memoryCap = 250 + bs2.memoryCap = 250 + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -483,27 +434,36 @@ func TestWsBlockLimiting(t *testing.T) { func TestRedirectExceptions(t *testing.T) { partitiontest.PartitionTest(t) - log := logging.TestingLog(t) + log1 := logging.TestingLog(t) + log2 := logging.TestingLog(t) ledger1 := makeLedger(t, "l1") + ledger2 := makeLedger(t, "l2") defer ledger1.Close() + defer ledger2.Close() addBlock(t, ledger1) net1 := &httpTestPeerSource{} - - config := config.GetDefaultLocal() - // Need to enable block service fallbacks - config.EnableBlockServiceFallbackToArchiver = true - - bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}") + net2 := &httpTestPeerSource{} nodeA := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB := &basicRPCNode{} nodeA.start() defer nodeA.stop() + nodeB.start() + defer nodeB.stop() + + configInvalidRedirects := config.GetDefaultLocal() + configInvalidRedirects.BlockServiceCustomFallbackEndpoints = "badAddress" + + configWithRedirectToSelf := config.GetDefaultLocal() + configWithRedirectToSelf.BlockServiceCustomFallbackEndpoints = nodeB.rootURL() - net1.peers = append(net1.peers, "invalidPeer") + bs1 := MakeBlockService(log1, configInvalidRedirects, ledger1, net1, "{genesisID}") + bs2 := MakeBlockService(log2, configWithRedirectToSelf, ledger2, net2, "{genesisID}") + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -515,7 +475,7 @@ func TestRedirectExceptions(t *testing.T) { blockURL := parsedURL.String() request, err := http.NewRequest("GET", blockURL, nil) require.NoError(t, err) - requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(config.CatchupHTTPBlockFetchTimeoutSec)*time.Second) + requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(configInvalidRedirects.CatchupHTTPBlockFetchTimeoutSec)*time.Second) defer requestCancel() request = request.WithContext(requestCtx) network.SetUserAgentHeader(request.Header) @@ -524,8 +484,14 @@ func TestRedirectExceptions(t *testing.T) { require.NoError(t, err) require.Equal(t, response.StatusCode, http.StatusNotFound) - net1.addPeer(nodeA.rootURL()) - _, err = client.Do(request) + parsedURLNodeB, err := network.ParseHostOrURL(nodeB.rootURL()) + require.NoError(t, err) + + parsedURLNodeB.Path = FormatBlockQuery(uint64(4), parsedURLNodeB.Path, net2) + blockURLNodeB := parsedURLNodeB.String() + requestNodeB, err := http.NewRequest("GET", blockURLNodeB, nil) + _, err = client.Do(requestNodeB) + require.Error(t, err) require.Contains(t, err.Error(), "stopped after 10 redirects") } diff --git a/test/testdata/configs/config-v33.json b/test/testdata/configs/config-v33.json index aa1cb71712..d9188ef748 100644 --- a/test/testdata/configs/config-v33.json +++ b/test/testdata/configs/config-v33.json @@ -43,8 +43,6 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, - "EnableBlockServiceFallbackToArchiver": false, - "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, From b5927a6aa668f40e4c2ae8c683a694add48ab7f9 Mon Sep 17 00:00:00 2001 From: nullun Date: Fri, 26 Jan 2024 20:12:41 +0000 Subject: [PATCH 076/218] netgoal: Use --last-part-key-round when generating a goalnet template (#5912) --- cmd/netgoal/generate.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/netgoal/generate.go b/cmd/netgoal/generate.go index 882cd0f9d0..9e87004a05 100644 --- a/cmd/netgoal/generate.go +++ b/cmd/netgoal/generate.go @@ -475,6 +475,9 @@ func saveTemplateToDisk(template remote.DeployedNetworkConfig, filename string) } func saveGoalTemplateToDisk(template netdeploy.NetworkTemplate, filename string) error { + if lastPartKeyRound != 0 { + template.Genesis.LastPartKeyRound = lastPartKeyRound + } f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err == nil { defer f.Close() From edf122de16f1602b562002434678238ad6d42ba1 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Mon, 5 Feb 2024 20:40:29 +0000 Subject: [PATCH 077/218] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index 573541ac97..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/config/version.go b/config/version.go index 272cff9f93..dce2bd533e 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 22 +const VersionMinor = 23 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From 1747abac65490cc1fb338daef8def45988dbc262 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:34:49 -0500 Subject: [PATCH 078/218] Blocks: Bump min to save based on catchpoint support (#5927) --- ledger/ledger.go | 16 ++++++++++- ledger/ledger_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/ledger/ledger.go b/ledger/ledger.go index fa1be1a766..110a72fc18 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -462,11 +462,25 @@ func (l *Ledger) notifyCommit(r basics.Round) basics.Round { if l.archival { // Do not forget any blocks. minToSave = 0 + } else { + catchpointsMinToSave := r.SubSaturate(l.calcMinCatchpointRoundsLookback()) + if catchpointsMinToSave < minToSave { + minToSave = catchpointsMinToSave + } } return minToSave } +func (l *Ledger) calcMinCatchpointRoundsLookback() basics.Round { + // cfg.StoresCatchpoints checks that CatchpointInterval is positive + if !l.cfg.StoresCatchpoints() || l.cfg.CatchpointFileHistoryLength == 0 { + return 0 + } + + return basics.Round(2 * l.cfg.CatchpointInterval) +} + // GetLastCatchpointLabel returns the latest catchpoint label that was written to the // database. func (l *Ledger) GetLastCatchpointLabel() string { @@ -901,7 +915,7 @@ func (l *Ledger) FlushCaches() { // Validate uses the ledger to validate block blk as a candidate next block. // It returns an error if blk is not the expected next block, or if blk is // not a valid block (e.g., it has duplicate transactions, overspends some -// account, etc). +// account, etc.). func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionPool execpool.BacklogPool) (*ledgercore.ValidatedBlock, error) { delta, err := eval.Eval(ctx, l, blk, true, l.verifiedTxnCache, executionPool, l.tracer) if err != nil { diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index ab5bc293a3..b18428741b 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -3287,3 +3287,67 @@ func TestLedgerMaxBlockHistoryLookback(t *testing.T) { require.Error(t, err) require.Empty(t, blk) } + +func TestLedgerRetainMinOffCatchpointInterval(t *testing.T) { + partitiontest.PartitionTest(t) + // This test is to ensure that the ledger retains the minimum number of blocks off the catchpoint interval. + blocksToMake := 2000 + + // Cases: + // 1. Base Case: Archival = false, Stores catchpoints returns true, CatchpointFileHistoryLength = >= 1 - implies catchpoint interval > 0 - min formula + // 2. Archival = true, stores catchpoints returns false - we keep all blocks anyway + // 3. Archival = false, stores catchpoints returns false - we don't modify minToSave + // 4. Condition: Archival = false, storesCatchpoints returns true, CatchpointFileHistoryLength is -1 - keep all catchpoint files + // 5. Condition: Archival = false, storesCatchpoints returns true, CatchpointFileHistoryLength is 365 - the config default setting + + catchpointIntervalBlockRetentionTestCases := []struct { + storeCatchpoints bool + archival bool + catchpointFileHistoryLength int + }{ + {true, false, 1}, // should use min catchpoint formula + {false, true, 1}, // all blocks get retained, archival mode dictates + {false, false, 1}, // should not modify min blocks retained based on catchpoint interval + {true, false, -1}, // should use min formula, this is the keep all catchpoints setting + {true, false, 365}, // should use min formula, this is the default setting for catchpoint file history length + } + for _, tc := range catchpointIntervalBlockRetentionTestCases { + func() { + var genHash crypto.Digest + crypto.RandBytes(genHash[:]) + cfg := config.GetDefaultLocal() + // set config properties based on test case + cfg.MaxBlockHistoryLookback = 0 // max block history lookback is not used in this test + if tc.storeCatchpoints { + cfg.CatchpointTracking = config.CatchpointTrackingModeStored + cfg.CatchpointInterval = 100 + } else { + cfg.CatchpointInterval = 0 // sufficient for cfg.StoresCatchpoints() to return false + } + cfg.CatchpointFileHistoryLength = tc.catchpointFileHistoryLength + cfg.Archival = tc.archival + + l := &Ledger{} + l.cfg = cfg + l.archival = cfg.Archival + + for i := 1; i <= blocksToMake; i++ { + minBlockToKeep := l.notifyCommit(basics.Round(i)) + + // In archival mode, all blocks should always be kept + if cfg.Archival { + require.Equal(t, basics.Round(0), minBlockToKeep) + } else { + // This happens to work for the test case where we don't store catchpoints since mintosave is always + // 0 in that case. + expectedCatchpointLookback := 2 * cfg.CatchpointInterval + + expectedMinBlockToKeep := basics.Round(uint64(i)).SubSaturate( + basics.Round(expectedCatchpointLookback)) + require.Equal(t, expectedMinBlockToKeep, minBlockToKeep) + } + } + }() + } + +} From e1db9e12612cd0640b9e069e83ac32363c549085 Mon Sep 17 00:00:00 2001 From: Halimao <1065621723@qq.com> Date: Wed, 7 Feb 2024 01:57:56 +0800 Subject: [PATCH 079/218] feat: add support to display mnemonic discreetly for `algokey generate` (#5886) --- cmd/algokey/common.go | 16 ++++++++++++++++ cmd/algokey/common_test.go | 39 ++++++++++++++++++++++++++++++++++++++ cmd/algokey/generate.go | 12 +++++++++++- go.mod | 5 +++++ go.sum | 10 ++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 cmd/algokey/common_test.go diff --git a/cmd/algokey/common.go b/cmd/algokey/common.go index 8fc5867db9..0259dc5e05 100644 --- a/cmd/algokey/common.go +++ b/cmd/algokey/common.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/passphrase" + "github.com/muesli/termenv" ) const ( @@ -122,3 +123,18 @@ func readFile(filename string) ([]byte, error) { } return os.ReadFile(filename) } + +// printDiscreetly Print a secret string to an alternate screen, +// so the string isn't printed to the terminal. +func printDiscreetly(w io.Writer, promptMsg, secretMsg string) error { + output := termenv.NewOutput(w) + output.AltScreen() + defer output.ExitAltScreen() + if _, err := fmt.Fprintf(output, "%s\n\n%s\n\nPress 'Enter' key to continue.", promptMsg, secretMsg); err != nil { + return err + } + if _, err := fmt.Scanln(); err != nil { + return err + } + return nil +} diff --git a/cmd/algokey/common_test.go b/cmd/algokey/common_test.go new file mode 100644 index 0000000000..2256342616 --- /dev/null +++ b/cmd/algokey/common_test.go @@ -0,0 +1,39 @@ +// Copyright (C) 2019-2024 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 main + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" +) + +func Test_printDiscreetly(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + buf := new(bytes.Buffer) + var ( + promptMsg = "Prompt Message" + secretMsg = "Secret Message" + ) + require.NoError(t, printDiscreetly(buf, promptMsg, secretMsg)) + require.Contains(t, buf.String(), promptMsg) + require.Contains(t, buf.String(), secretMsg) +} diff --git a/cmd/algokey/generate.go b/cmd/algokey/generate.go index 6382fa3a0c..6b1c409e68 100644 --- a/cmd/algokey/generate.go +++ b/cmd/algokey/generate.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "os" "github.com/spf13/cobra" @@ -27,10 +28,12 @@ import ( var generateKeyfile string var generatePubkeyfile string +var generateDiscreet bool func init() { generateCmd.Flags().StringVarP(&generateKeyfile, "keyfile", "f", "", "Private key filename") generateCmd.Flags().StringVarP(&generatePubkeyfile, "pubkeyfile", "p", "", "Public key filename") + generateCmd.Flags().BoolVar(&generateDiscreet, "discreet", false, "Print mnemonic discreetly to an alternate screen") } var generateCmd = &cobra.Command{ @@ -46,7 +49,14 @@ var generateCmd = &cobra.Command{ key := crypto.GenerateSignatureSecrets(seed) publicKeyChecksummed := basics.Address(key.SignatureVerifier).String() - fmt.Printf("Private key mnemonic: %s\n", mnemonic) + if generateDiscreet { + if err := printDiscreetly(os.Stderr, "**Important** write this private key mnemonic phrase in a safe place. Do not share it to anyone", fmt.Sprintf("Private key mnemonic: %s", mnemonic)); err != nil { + fmt.Fprintf(os.Stderr, "Fail to print mnemonic: %v", err) + os.Exit(1) + } + } else { + fmt.Printf("Private key mnemonic: %s\n", mnemonic) + } fmt.Printf("Public key: %s\n", publicKeyChecksummed) if generateKeyfile != "" { diff --git a/go.mod b/go.mod index 8844a80585..0d85043883 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/libp2p/go-yamux/v4 v4.0.1 github.com/mattn/go-sqlite3 v1.14.16 github.com/miekg/dns v1.1.55 + github.com/muesli/termenv v0.15.2 github.com/multiformats/go-multiaddr v0.10.1 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olivere/elastic v6.2.14+incompatible @@ -51,6 +52,7 @@ require ( require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // 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 @@ -107,10 +109,12 @@ require ( 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/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -142,6 +146,7 @@ require ( 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/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1d4e5abf63..f8d0ac3a04 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 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= @@ -430,6 +432,8 @@ github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjP 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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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= @@ -451,6 +455,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky 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-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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= @@ -491,6 +497,8 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 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= @@ -591,6 +599,8 @@ github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2Ep 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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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= From caec33dfe083327994572d77b66a8a0c6d40bc2c Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 13 Feb 2024 12:35:28 -0500 Subject: [PATCH 080/218] CICD: fix package signing issues (#5934) --- package-deploy.yaml | 1 - .../mule/common/ensure_centos8_image.sh | 17 ++++++++ scripts/release/mule/deploy/deb/deploy.sh | 25 +++-------- scripts/release/mule/deploy/docker/docker.sh | 6 +-- .../releases_page/generate_releases_page.sh | 9 +--- scripts/release/mule/deploy/rpm/deploy.sh | 42 ++++++++++--------- scripts/release/mule/sign/sign.sh | 24 ++++++----- test/muleCI/mule.yaml | 35 ---------------- 8 files changed, 64 insertions(+), 95 deletions(-) create mode 100755 scripts/release/mule/common/ensure_centos8_image.sh diff --git a/package-deploy.yaml b/package-deploy.yaml index 8daf262acc..9b67a2fe6c 100644 --- a/package-deploy.yaml +++ b/package-deploy.yaml @@ -56,7 +56,6 @@ agents: - NETWORK=$NETWORK - NO_DEPLOY=$NO_DEPLOY - PACKAGES_DIR=$PACKAGES_DIR - - S3_SOURCE=$S3_SOURCE - STAGING=$STAGING - VERSION=$VERSION volumes: diff --git a/scripts/release/mule/common/ensure_centos8_image.sh b/scripts/release/mule/common/ensure_centos8_image.sh new file mode 100755 index 0000000000..1ebd3475fe --- /dev/null +++ b/scripts/release/mule/common/ensure_centos8_image.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -exo pipefail + +# Ensure the centos8 docker image is built and available + +DOCKER_IMAGE="algorand/go-algorand-ci-linux-centos8:amd64-$(sha1sum scripts/configure_dev-deps.sh | cut -f1 -d' ')" +MATCH=${DOCKER_IMAGE/:*/} + +echo "Checking for RPM image" +if docker images $DOCKER_IMAGE | grep -qs $MATCH > /dev/null 2>&1; then + echo "Image exists" +else + echo "RPM image doesn't exist, building" + docker build --platform=linux/amd64 --build-arg ARCH=amd64 \ + --build-arg GOLANG_VERSION=$(./scripts/get_golang_version.sh) -t $DOCKER_IMAGE -f docker/build/cicd.centos8.Dockerfile . +fi diff --git a/scripts/release/mule/deploy/deb/deploy.sh b/scripts/release/mule/deploy/deb/deploy.sh index c9c4b4b6c4..2584b84123 100755 --- a/scripts/release/mule/deploy/deb/deploy.sh +++ b/scripts/release/mule/deploy/deb/deploy.sh @@ -2,28 +2,13 @@ set -ex -if [ -z "$NETWORK" ] -then - echo "[$0] Network is a required parameter." - exit 1 -fi - -if [ -z "$STAGING" ] -then - echo "[$0] Staging is a required parameter." - exit 1 -fi - -CHANNEL=$("./scripts/release/mule/common/get_channel.sh" "$NETWORK") +CHANNEL=${CHANNEL:-$("./scripts/release/mule/common/get_channel.sh" "$NETWORK")} VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} +PACKAGES_DIR=${PACKAGES_DIR:-~/packages} +SNAPSHOT=${SNAPSHOT:-"${CHANNEL}-${VERSION}"} -if [ -z "$SNAPSHOT" ] -then - SNAPSHOT="$CHANNEL-$VERSION" -fi - -PACKAGES_DIR=/root/packages -mkdir -p /root/packages +mkdir -p $PACKAGES_DIR +rm -f $PACKAGES_DIR/*.deb aptly mirror update stable aptly mirror update beta diff --git a/scripts/release/mule/deploy/docker/docker.sh b/scripts/release/mule/deploy/docker/docker.sh index ee0c55fe0c..093922fd3d 100755 --- a/scripts/release/mule/deploy/docker/docker.sh +++ b/scripts/release/mule/deploy/docker/docker.sh @@ -13,9 +13,9 @@ if [ -z "$NETWORK" ] || [ -z "$VERSION" ]; then exit 1 fi -if [[ ! "$NETWORK" =~ ^mainnet$|^testnet$|^betanet$|^alphanet$ ]] +if [[ ! "$NETWORK" =~ ^mainnet$|^testnet$|^betanet$ ]] then - echo "[$0] Network values must be either \`mainnet\`, \`testnet\`, \`betanet\`, or \`alphanet\`." + echo "[$0] Network values must be either \`mainnet\`, \`testnet\`, or \`betanet\`." exit 1 fi @@ -28,7 +28,7 @@ then # Build and push testnet. ./build_releases.sh --tagname "$VERSION" --network testnet --cached -elif [ "$NETWORK" = betanet ] || [ "$NETWORK" = alphanet ] +elif [ "$NETWORK" = betanet ] then ./build_releases.sh --tagname "$VERSION" --network "$NETWORK" fi diff --git a/scripts/release/mule/deploy/releases_page/generate_releases_page.sh b/scripts/release/mule/deploy/releases_page/generate_releases_page.sh index 5b6a488ce2..75df10d524 100755 --- a/scripts/release/mule/deploy/releases_page/generate_releases_page.sh +++ b/scripts/release/mule/deploy/releases_page/generate_releases_page.sh @@ -8,13 +8,8 @@ set -ex -if [ -z "$NETWORK" ] || [ -z "$VERSION" ] -then - echo "[$0] Network and version are required parameters." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} +VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} cd scripts/release/mule/deploy/releases_page diff --git a/scripts/release/mule/deploy/rpm/deploy.sh b/scripts/release/mule/deploy/rpm/deploy.sh index f660f1d01b..b96cccd6cb 100755 --- a/scripts/release/mule/deploy/rpm/deploy.sh +++ b/scripts/release/mule/deploy/rpm/deploy.sh @@ -7,28 +7,25 @@ echo date "+build_release begin DEPLOY rpm stage %Y%m%d_%H%M%S" echo -if [ -z "$NETWORK" ]; then - echo "[$0] NETWORK is missing." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} 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"} +PACKAGES_DIR=${PACKAGES_DIR:-"tmp"} if [ -n "$S3_SOURCE" ] then PREFIX="$S3_SOURCE/$CHANNEL/$VERSION" 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 + aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.aarch64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.aarch64.rpm" $PACKAGES_DIR 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 + aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.aarch64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.aarch64.rpm" $PACKAGES_DIR fi else cp "$PACKAGES_DIR"/*"$VERSION"*.rpm /root @@ -56,11 +53,7 @@ cat << EOF > .rpmmacros EOF mkdir rpmrepo -for rpm in $(ls *"$VERSION"*.rpm) -do - rpmsign --addsign "$rpm" - cp -p "$rpm" rpmrepo -done +mv -f *"$VERSION"*.rpm rpmrepo createrepo --database rpmrepo rm -f rpmrepo/repodata/repomd.xml.asc @@ -72,8 +65,19 @@ 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' + if [ -n "$S3_SOURCE" ]; then + # if S3_SOURCE exists, we copied files from s3 + echo "Copy signatures from s3 staging to s3 releases" + aws s3 sync s3://algorand-staging/releases/$CHANNEL/ s3://algorand-releases/rpm/sigs/$CHANNEL/ --exclude='*' --include='*.rpm.sig' + + else + # We are working with files locally + popd + echo "Copy local signatures to s3 releases" + aws s3 sync "$PACKAGES_DIR" "s3://algorand-releases/rpm/sigs/$CHANNEL/" --exclude='*' --include='*.rpm.sig' + fi fi echo diff --git a/scripts/release/mule/sign/sign.sh b/scripts/release/mule/sign/sign.sh index e08e2d52d3..89baedb9ce 100755 --- a/scripts/release/mule/sign/sign.sh +++ b/scripts/release/mule/sign/sign.sh @@ -10,18 +10,13 @@ echo date "+build_release begin SIGN stage %Y%m%d_%H%M%S" echo -if [ -z "$NETWORK" ]; then - echo "[$0] NETWORK is missing." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} PKG_DIR="./tmp/node_pkgs" SIGNING_KEY_ADDR=dev@algorand.com OS_TYPE=$(./scripts/release/mule/common/ostype.sh) -ARCHS=(amd64 arm arm64) -ARCH_BITS=(x86_64 armv7l aarch64) +ARCHS=(amd64 arm64) +ARCH_BITS=(x86_64 aarch64) # Note that we don't want to use $GNUPGHOME here because that is a documented env var for the gnupg # project and if it's set in the environment mule will automatically pick it up, which could have # unintended consequences and be hard to debug. @@ -40,6 +35,14 @@ then find "$GPG_DIR" -type f -exec chmod 600 {} \; fi +pushd /root +cat << EOF > .rpmmacros +%_gpg_name Algorand RPM +%__gpg /usr/bin/gpg2 +%__gpg_check_password_cmd true +EOF +popd + # Note that when downloading from the cloud that we'll get all packages for all architectures. if [ -n "$S3_SOURCE" ] then @@ -87,13 +90,14 @@ for os in "${OS_TYPES[@]}"; do for file in *.rpm do + rpmsign --addsign "$file" gpg -u rpm@algorand.com --detach-sign "$file" done HASHFILE="hashes_${CHANNEL}_${os}_${arch}_${VERSION}" md5sum *.tar.gz *.deb *.rpm >> "$HASHFILE" - shasum -a 256 *.tar.gz *.deb *.rpm >> "$HASHFILE" - shasum -a 512 *.tar.gz *.deb *.rpm >> "$HASHFILE" + sha256sum *.tar.gz *.deb *.rpm >> "$HASHFILE" + sha512sum *.tar.gz *.deb *.rpm >> "$HASHFILE" gpg -u "$SIGNING_KEY_ADDR" --detach-sign "$HASHFILE" gpg -u "$SIGNING_KEY_ADDR" --clearsign "$HASHFILE" diff --git a/test/muleCI/mule.yaml b/test/muleCI/mule.yaml index cb75187db4..afa73ae2c1 100644 --- a/test/muleCI/mule.yaml +++ b/test/muleCI/mule.yaml @@ -61,22 +61,6 @@ agents: - GOLANG_VERSION=`./scripts/get_golang_version.sh` - ARCH=arm64v8 - GOARCH=arm64 - - name: cicd.ubuntu.arm - dockerFilePath: docker/build/cicd.ubuntu.Dockerfile - image: algorand/go-algorand-ci-linux - version: scripts/configure_dev-deps.sh - arch: arm32v7 - env: - - TRAVIS_BRANCH=${GIT_BRANCH} - - NETWORK=$NETWORK - - VERSION=$VERSION - - BUILD_NUMBER=$BUILD_NUMBER - - GOHOSTARCH=arm - - FULLVERSION=${FULLVERSION} - buildArgs: - - GOLANG_VERSION=`./scripts/get_golang_version.sh` - - ARCH=arm32v7 - - GOARCH=armv6l - name: docker-ubuntu dockerFilePath: docker/build/docker.ubuntu.Dockerfile image: algorand/go-algorand-docker-linux-ubuntu @@ -122,10 +106,6 @@ tasks: name: build.arm64 agent: cicd.ubuntu.arm64 target: ci-build - - task: docker.Make - name: build.arm - agent: cicd.ubuntu.arm - target: ci-build - task: docker.Make name: archive @@ -167,12 +147,6 @@ tasks: stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm64 globSpecs: - tmp/node_pkgs/**/* - - task: stash.Stash - name: linux-arm - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm - globSpecs: - - tmp/node_pkgs/**/* - task: stash.Stash name: packages bucketName: go-algorand-ci-cache @@ -193,10 +167,6 @@ tasks: name: darwin-amd64 bucketName: go-algorand-ci-cache stashId: ${JENKINS_JOB_CACHE_ID}/darwin-amd64 - - task: stash.Unstash - name: linux-arm - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm - task: stash.Unstash name: darwin-arm64 bucketName: go-algorand-ci-cache @@ -233,15 +203,10 @@ jobs: tasks: - docker.Make.build.arm64 - stash.Stash.linux-arm64 - build-linux-arm32: - tasks: - - docker.Make.build.arm - - stash.Stash.linux-arm package-all: tasks: - stash.Unstash.linux-amd64 - stash.Unstash.linux-arm64 - - stash.Unstash.linux-arm - stash.Unstash.darwin-arm64 - stash.Unstash.darwin-amd64 - docker.Make.deb.amd64 From d8c825d96b90b4f1c84a5b3771987ab53bfc8568 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:43:32 -0500 Subject: [PATCH 081/218] network: use network context for DNS operations in readFromSRV (#5936) --- cmd/catchpointdump/net.go | 2 +- network/wsNetwork.go | 6 +++--- network/wsNetwork_test.go | 2 +- tools/network/bootstrap.go | 14 +++++++------- tools/network/bootstrap_test.go | 5 +++-- tools/network/telemetryURIUpdateService.go | 3 ++- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/cmd/catchpointdump/net.go b/cmd/catchpointdump/net.go index 2de40c3c2d..41e1fd1dd0 100644 --- a/cmd/catchpointdump/net.go +++ b/cmd/catchpointdump/net.go @@ -78,7 +78,7 @@ var netCmd = &cobra.Command{ if relayAddress != "" { addrs = []string{relayAddress} } else { - addrs, err = tools.ReadFromSRV("algobootstrap", "tcp", networkName, "", false) + addrs, err = tools.ReadFromSRV(context.Background(), "algobootstrap", "tcp", networkName, "", false) if err != nil || len(addrs) == 0 { reportErrorf("Unable to bootstrap records for '%s' : %v", networkName, err) } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 5bfabbaf2b..92a02976b4 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -289,7 +289,7 @@ type WebsocketNetwork struct { protocolVersion string // resolveSRVRecords is a function that resolves SRV records for a given service, protocol and name - resolveSRVRecords func(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) + resolveSRVRecords func(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) } const ( @@ -1887,7 +1887,7 @@ func (wn *WebsocketNetwork) mergePrimarySecondaryRelayAddressSlices(network prot func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archiverAddresses []string) { var err error - relaysAddresses, err = wn.resolveSRVRecords("algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + relaysAddresses, err = wn.resolveSRVRecords(wn.ctx, "algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { // only log this warning on testnet or devnet if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { @@ -1896,7 +1896,7 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses [] relaysAddresses = nil } - archiverAddresses, err = wn.resolveSRVRecords("archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + archiverAddresses, err = wn.resolveSRVRecords(wn.ctx, "archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { // only log this warning on testnet or devnet if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index e35cc7d177..ef5769bcff 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -4156,7 +4156,7 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { } // Mock the SRV record lookup - netA.resolveSRVRecords = func(service string, protocol string, name string, fallbackDNSResolverAddress string, + netA.resolveSRVRecords = func(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { return primaryRelayResolvedRecords, nil diff --git a/tools/network/bootstrap.go b/tools/network/bootstrap.go index f04c67528c..d30ae4bda0 100644 --- a/tools/network/bootstrap.go +++ b/tools/network/bootstrap.go @@ -24,7 +24,7 @@ import ( "github.com/algorand/go-algorand/logging" ) -func readFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (records []*net.SRV, err error) { +func readFromSRV(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (records []*net.SRV, err error) { log := logging.Base() if name == "" { log.Debug("no dns lookup due to empty name") @@ -38,14 +38,14 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv controller := NewResolveController(secure, fallbackDNSResolverAddress, log) systemResolver := controller.SystemResolver() - _, records, sysLookupErr := systemResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, sysLookupErr := systemResolver.LookupSRV(ctx, service, protocol, name) if sysLookupErr != nil { log.Infof("ReadFromBootstrap: DNS LookupSRV failed when using system resolver: %v", sysLookupErr) var fallbackLookupErr error if fallbackDNSResolverAddress != "" { fallbackResolver := controller.FallbackResolver() - _, records, fallbackLookupErr = fallbackResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, fallbackLookupErr = fallbackResolver.LookupSRV(ctx, service, protocol, name) } if fallbackLookupErr != nil { log.Infof("ReadFromBootstrap: DNS LookupSRV failed when using fallback '%s' resolver: %v", fallbackDNSResolverAddress, fallbackLookupErr) @@ -54,7 +54,7 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv if fallbackLookupErr != nil || fallbackDNSResolverAddress == "" { fallbackResolver := controller.DefaultResolver() var defaultLookupErr error - _, records, defaultLookupErr = fallbackResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, defaultLookupErr = fallbackResolver.LookupSRV(ctx, service, protocol, name) if defaultLookupErr != nil { err = fmt.Errorf("ReadFromBootstrap: DNS LookupSRV failed when using system resolver(%v), fallback resolver(%v), as well as using default resolver due to %v", sysLookupErr, fallbackLookupErr, defaultLookupErr) return @@ -65,8 +65,8 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv } // ReadFromSRV is a helper to collect SRV addresses for a given name -func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { - records, err := readFromSRV(service, protocol, name, fallbackDNSResolverAddress, secure) +func ReadFromSRV(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { + records, err := readFromSRV(ctx, service, protocol, name, fallbackDNSResolverAddress, secure) if err != nil { return addrs, err } @@ -88,7 +88,7 @@ func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolv // ReadFromSRVPriority is a helper to collect SRV addresses with priorities for a given name func ReadFromSRVPriority(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (prioAddrs map[uint16][]string, err error) { - records, err := readFromSRV(service, protocol, name, fallbackDNSResolverAddress, secure) + records, err := readFromSRV(context.Background(), service, protocol, name, fallbackDNSResolverAddress, secure) if err != nil { return prioAddrs, err } diff --git a/tools/network/bootstrap_test.go b/tools/network/bootstrap_test.go index 155615c704..a24bea422e 100644 --- a/tools/network/bootstrap_test.go +++ b/tools/network/bootstrap_test.go @@ -17,6 +17,7 @@ package network import ( + "context" "testing" "github.com/algorand/go-algorand/test/partitiontest" @@ -55,10 +56,10 @@ func TestReadFromSRV(t *testing.T) { fallback := "" secure := true - addrs, err := ReadFromSRV("", protocol, name, fallback, secure) + addrs, err := ReadFromSRV(context.Background(), "", protocol, name, fallback, secure) require.Error(t, err) - addrs, err = ReadFromSRV(service, protocol, name, fallback, secure) + addrs, err = ReadFromSRV(context.Background(), service, protocol, name, fallback, secure) require.NoError(t, err) require.GreaterOrEqual(t, len(addrs), 1) addr := addrs[0] diff --git a/tools/network/telemetryURIUpdateService.go b/tools/network/telemetryURIUpdateService.go index 66dd87dd0e..2b4e614261 100644 --- a/tools/network/telemetryURIUpdateService.go +++ b/tools/network/telemetryURIUpdateService.go @@ -17,6 +17,7 @@ package network import ( + "context" "net/url" "strings" "time" @@ -132,5 +133,5 @@ func (t *telemetryURIUpdater) lookupTelemetryURL() (url *url.URL) { } func (t *telemetryURIUpdater) readFromSRV(protocol string, bootstrapID string) (addrs []string, err error) { - return ReadFromSRV("telemetry", protocol, bootstrapID, t.cfg.FallbackDNSResolverAddress, t.cfg.DNSSecuritySRVEnforced()) + return ReadFromSRV(context.Background(), "telemetry", protocol, bootstrapID, t.cfg.FallbackDNSResolverAddress, t.cfg.DNSSecuritySRVEnforced()) } From 52964edd9795c4cacc7d107ac9f41f73374fce71 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:33:35 -0500 Subject: [PATCH 082/218] Network: Class-based Peer Selector (#5937) --- catchup/catchpointService.go | 80 ++-- catchup/classBasedPeerSelector.go | 156 ++++++++ catchup/classBasedPeerSelector_test.go | 498 +++++++++++++++++++++++++ catchup/peerSelector.go | 34 +- catchup/peerSelector_test.go | 22 +- catchup/service.go | 108 +++--- catchup/service_test.go | 111 +----- 7 files changed, 789 insertions(+), 220 deletions(-) create mode 100644 catchup/classBasedPeerSelector.go create mode 100644 catchup/classBasedPeerSelector_test.go diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 2c4f6dfc41..3c11d0db73 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -18,6 +18,7 @@ package catchup import ( "context" + "errors" "fmt" "sync" "time" @@ -69,7 +70,7 @@ type CatchpointCatchupStats struct { type CatchpointCatchupService struct { // stats is the statistics object, updated async while downloading the ledger stats CatchpointCatchupStats - // statsMu synchronizes access to stats, as we could attempt to update it while querying for it's current state + // statsMu synchronizes access to stats, as we could attempt to update it while querying for its current state statsMu deadlock.Mutex node CatchpointCatchupNodeServices // ctx is the node cancellation context, used when the node is being stopped. @@ -98,7 +99,7 @@ type CatchpointCatchupService struct { abortCtx context.Context abortCtxFunc context.CancelFunc // blocksDownloadPeerSelector is the peer selector used for downloading blocks. - blocksDownloadPeerSelector *peerSelector + blocksDownloadPeerSelector peerSelector } // MakeResumedCatchpointCatchupService creates a catchpoint catchup service for a node that is already in catchpoint catchup mode @@ -280,51 +281,50 @@ func (cs *CatchpointCatchupService) processStageInactive() (err error) { } // processStageLedgerDownload is the second catchpoint catchup stage. It downloads the ledger. -func (cs *CatchpointCatchupService) processStageLedgerDownload() (err error) { +func (cs *CatchpointCatchupService) processStageLedgerDownload() error { cs.statsMu.Lock() label := cs.stats.CatchpointLabel cs.statsMu.Unlock() - round, _, err0 := ledgercore.ParseCatchpointLabel(label) + round, _, err := ledgercore.ParseCatchpointLabel(label) - if err0 != nil { - return cs.abort(fmt.Errorf("processStageLedgerDownload failed to parse label : %v", err0)) + if err != nil { + return cs.abort(fmt.Errorf("processStageLedgerDownload failed to parse label : %v", err)) } // download balances file. - peerSelector := cs.makeCatchpointPeerSelector() - ledgerFetcher := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) + lf := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) attemptsCount := 0 for { attemptsCount++ - err = cs.ledgerAccessor.ResetStagingBalances(cs.ctx, true) - if err != nil { + err0 := cs.ledgerAccessor.ResetStagingBalances(cs.ctx, true) + if err0 != nil { if cs.ctx.Err() != nil { return cs.stopOrAbort() } - return cs.abort(fmt.Errorf("processStageLedgerDownload failed to reset staging balances : %v", err)) + return cs.abort(fmt.Errorf("processStageLedgerDownload failed to reset staging balances : %v", err0)) } - psp, err := peerSelector.getNextPeer() - if err != nil { - err = fmt.Errorf("processStageLedgerDownload: catchpoint catchup was unable to obtain a list of peers to retrieve the catchpoint file from") - return cs.abort(err) + psp, err0 := cs.blocksDownloadPeerSelector.getNextPeer() + if err0 != nil { + err0 = fmt.Errorf("processStageLedgerDownload: catchpoint catchup was unable to obtain a list of peers to retrieve the catchpoint file from") + return cs.abort(err0) } peer := psp.Peer start := time.Now() - err = ledgerFetcher.downloadLedger(cs.ctx, peer, round) - if err == nil { + err0 = lf.downloadLedger(cs.ctx, peer, round) + if err0 == nil { cs.log.Infof("ledger downloaded in %d seconds", time.Since(start)/time.Second) start = time.Now() - err = cs.ledgerAccessor.BuildMerkleTrie(cs.ctx, cs.updateVerifiedCounts) - if err == nil { + err0 = cs.ledgerAccessor.BuildMerkleTrie(cs.ctx, cs.updateVerifiedCounts) + if err0 == nil { cs.log.Infof("built merkle trie in %d seconds", time.Since(start)/time.Second) break } // failed to build the merkle trie for the above catchpoint file. - peerSelector.rankPeer(psp, peerRankInvalidDownload) + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankInvalidDownload) } else { - peerSelector.rankPeer(psp, peerRankDownloadFailed) + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankDownloadFailed) } // instead of testing for err == cs.ctx.Err() , we'll check on the context itself. @@ -335,10 +335,10 @@ func (cs *CatchpointCatchupService) processStageLedgerDownload() (err error) { } if attemptsCount >= cs.config.CatchupLedgerDownloadRetryAttempts { - err = fmt.Errorf("processStageLedgerDownload: catchpoint catchup exceeded number of attempts to retrieve ledger") - return cs.abort(err) + err0 = fmt.Errorf("processStageLedgerDownload: catchpoint catchup exceeded number of attempts to retrieve ledger") + return cs.abort(err0) } - cs.log.Warnf("unable to download ledger : %v", err) + cs.log.Warnf("unable to download ledger : %v", err0) } err = cs.updateStage(ledger.CatchpointCatchupStateLatestBlockDownload) @@ -506,14 +506,14 @@ func lookbackForStateproofsSupport(topBlock *bookkeeping.Block) uint64 { return uint64(topBlock.Round().SubSaturate(lowestStateProofRound)) } -// processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against it's predecessor. +// processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against its predecessor. func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { topBlock, err := cs.ledgerAccessor.EnsureFirstBlock(cs.ctx) if err != nil { return cs.abort(fmt.Errorf("processStageBlocksDownload failed, unable to ensure first block : %v", err)) } - // pick the lookback with the greater of + // pick the lookback with the greatest of // either (MaxTxnLife+DeeperBlockHeaderHistory+CatchpointLookback) or MaxBalLookback // Explanation: // 1. catchpoint snapshots accounts at round X-CatchpointLookback @@ -531,13 +531,13 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { } // in case the effective lookback is going before our rounds count, trim it there. - // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback..MaxTxnLife) + // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback...MaxTxnLife) if lookback >= uint64(topBlock.Round()) { lookback = uint64(topBlock.Round() - 1) } cs.statsMu.Lock() - cs.stats.TotalBlocks = uint64(lookback) + cs.stats.TotalBlocks = lookback cs.stats.AcquiredBlocks = 0 cs.stats.VerifiedBlocks = 0 cs.statsMu.Unlock() @@ -558,8 +558,9 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { blk = &ledgerBlock cert = &ledgerCert } else { - switch err0.(type) { - case ledgercore.ErrNoEntry: + var errNoEntry ledgercore.ErrNoEntry + switch { + case errors.As(err0, &errNoEntry): // this is expected, ignore this one. default: cs.log.Warnf("processStageBlocksDownload encountered the following error when attempting to retrieve the block for round %d : %v", topBlock.Round()-basics.Round(blocksFetched), err0) @@ -658,7 +659,7 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { func (cs *CatchpointCatchupService) fetchBlock(round basics.Round, retryCount uint64) (blk *bookkeeping.Block, cert *agreement.Certificate, downloadDuration time.Duration, psp *peerSelectorPeer, stop bool, err error) { psp, err = cs.blocksDownloadPeerSelector.getNextPeer() if err != nil { - if err == errPeerSelectorNoPeerPoolsAvailable { + if errors.Is(err, errPeerSelectorNoPeerPoolsAvailable) { cs.log.Infof("fetchBlock: unable to obtain a list of peers to retrieve the latest block from; will retry shortly.") // this is a possible on startup, since the network package might have yet to retrieve the list of peers. time.Sleep(noPeersAvailableSleepInterval) @@ -718,7 +719,7 @@ func (cs *CatchpointCatchupService) processStageSwitch() (err error) { // stopOrAbort is called when any of the stage processing function sees that cs.ctx has been canceled. It can be // due to the end user attempting to abort the current catchpoint catchup operation or due to a node shutdown. func (cs *CatchpointCatchupService) stopOrAbort() error { - if cs.abortCtx.Err() == context.Canceled { + if errors.Is(cs.abortCtx.Err(), context.Canceled) { return cs.abort(context.Canceled) } return nil @@ -749,7 +750,7 @@ func (cs *CatchpointCatchupService) updateStage(newStage ledger.CatchpointCatchu return nil } -// updateNodeCatchupMode requests the node to change it's operational mode from +// updateNodeCatchupMode requests the node to change its operational mode from // catchup mode to normal mode and vice versa. func (cs *CatchpointCatchupService) updateNodeCatchupMode(catchupModeEnabled bool) { newCtxCh := cs.node.SetCatchpointCatchupMode(catchupModeEnabled) @@ -802,15 +803,7 @@ func (cs *CatchpointCatchupService) updateBlockRetrievalStatistics(acquiredBlock } func (cs *CatchpointCatchupService) initDownloadPeerSelector() { - cs.blocksDownloadPeerSelector = cs.makeCatchpointPeerSelector() -} - -func (cs *CatchpointCatchupService) makeCatchpointPeerSelector() *peerSelector { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, - }) + cs.blocksDownloadPeerSelector = makeCatchpointPeerSelector(cs.net) } // checkLedgerDownload sends a HEAD request to the ledger endpoint of peers to validate the catchpoint's availability @@ -821,10 +814,9 @@ func (cs *CatchpointCatchupService) checkLedgerDownload() error { if err != nil { return fmt.Errorf("failed to parse catchpoint label : %v", err) } - peerSelector := cs.makeCatchpointPeerSelector() ledgerFetcher := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) for i := 0; i < cs.config.CatchupLedgerDownloadRetryAttempts; i++ { - psp, peerError := peerSelector.getNextPeer() + psp, peerError := cs.blocksDownloadPeerSelector.getNextPeer() if peerError != nil { return err } diff --git a/catchup/classBasedPeerSelector.go b/catchup/classBasedPeerSelector.go new file mode 100644 index 0000000000..9ab9e6d71d --- /dev/null +++ b/catchup/classBasedPeerSelector.go @@ -0,0 +1,156 @@ +// Copyright (C) 2019-2024 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 catchup + +import ( + "errors" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-deadlock" + "time" +) + +// classBasedPeerSelector is a rankPooledPeerSelector that tracks and ranks classes of peers based on their response behavior. +// It is used to select the most appropriate peers to download blocks from - this is most useful when catching up +// and needing to figure out whether the blocks can be retrieved from relay nodes or require archive nodes. +// The ordering of the peerSelectors directly determines the priority of the classes of peers. +type classBasedPeerSelector struct { + mu deadlock.Mutex + peerSelectors []*wrappedPeerSelector +} + +func makeClassBasedPeerSelector(peerSelectors []*wrappedPeerSelector) *classBasedPeerSelector { + return &classBasedPeerSelector{ + peerSelectors: peerSelectors, + } +} + +func (c *classBasedPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { + c.mu.Lock() + defer c.mu.Unlock() + + oldRank, newRank := -1, -1 + for _, wp := range c.peerSelectors { + // See if the peer is in the class, ranking it appropriately if so + if psp.peerClass != wp.peerClass { + continue + } + + oldRank, newRank = wp.peerSelector.rankPeer(psp, rank) + if oldRank < 0 || newRank < 0 { + // Peer not found in this selector + continue + } + + // Peer was in this class, if there was any kind of download issue, we increment the failure count + if rank >= peerRankNoBlockForRound { + wp.downloadFailures++ + } + + break + } + + return oldRank, newRank +} + +func (c *classBasedPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + c.mu.Lock() + defer c.mu.Unlock() + + for _, wp := range c.peerSelectors { + rank = wp.peerSelector.peerDownloadDurationToRank(psp, blockDownloadDuration) + // If rank is peerRankInvalidDownload, we check the next class's rankPooledPeerSelector + if rank >= peerRankInvalidDownload { + continue + } + // Should be a legit ranking, we return it + return rank + } + // If we reached here, we have exhausted all classes without finding the peer + return peerRankInvalidDownload +} + +func (c *classBasedPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { + c.mu.Lock() + defer c.mu.Unlock() + return c.internalGetNextPeer(0) +} + +// internalGetNextPeer is a helper function that should be called with the lock held +func (c *classBasedPeerSelector) internalGetNextPeer(recurseCount int8) (psp *peerSelectorPeer, err error) { + // Safety check to prevent infinite recursion + if recurseCount > 1 { + return nil, errPeerSelectorNoPeerPoolsAvailable + } + selectorDisabledCount := 0 + for _, wp := range c.peerSelectors { + if wp.downloadFailures > wp.toleranceFactor { + // peerSelector is disabled for now, we move to the next one + selectorDisabledCount++ + continue + } + psp, err = wp.peerSelector.getNextPeer() + + if err != nil { + // This is mostly just future-proofing, as we don't expect any other errors from getNextPeer + if errors.Is(err, errPeerSelectorNoPeerPoolsAvailable) { + // We penalize this class the equivalent of one download failure (in case this is transient) + wp.downloadFailures++ + } + continue + } + return psp, nil + } + // If we reached here, we have exhausted all classes and still have no peers + // IFF all classes are disabled, we reset the downloadFailures for all classes and start over + if len(c.peerSelectors) != 0 && selectorDisabledCount == len(c.peerSelectors) { + for _, wp := range c.peerSelectors { + wp.downloadFailures = 0 + } + // Recurse to try again, we should have at least one class enabled now + return c.internalGetNextPeer(recurseCount + 1) + } + // If we reached here, we have exhausted all classes without finding a peer, not due to all classes being disabled + return nil, errPeerSelectorNoPeerPoolsAvailable +} + +type wrappedPeerSelector struct { + peerSelector peerSelector // The underlying peerSelector for this class + peerClass network.PeerOption // The class of peers the peerSelector is responsible for + toleranceFactor int // The number of times we can net fail for any reason before we move to the next class's rankPooledPeerSelector + downloadFailures int // The number of times we have failed to download a block from this class's rankPooledPeerSelector since it was last reset +} + +// makeCatchpointPeerSelector returns a classBasedPeerSelector that selects peers based on their class and response behavior. +// These are the preferred configurations for the catchpoint service. +func makeCatchpointPeerSelector(net peersRetriever) peerSelector { + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersPhonebookRelays, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}), + toleranceFactor: 10, + }, + } + + return makeClassBasedPeerSelector(wrappedPeerSelectors) +} diff --git a/catchup/classBasedPeerSelector_test.go b/catchup/classBasedPeerSelector_test.go new file mode 100644 index 0000000000..0110663f87 --- /dev/null +++ b/catchup/classBasedPeerSelector_test.go @@ -0,0 +1,498 @@ +// Copyright (C) 2019-2024 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 catchup + +import ( + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +// Use to mock the wrapped peer selectors where warranted +type mockPeerSelector struct { + mockRankPeer func(psp *peerSelectorPeer, rank int) (int, int) + mockPeerDownloadDurationToRank func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) + mockGetNextPeer func() (psp *peerSelectorPeer, err error) +} + +func (m mockPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { + return m.mockRankPeer(psp, rank) +} + +func (m mockPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return m.mockPeerDownloadDurationToRank(psp, blockDownloadDuration) +} + +func (m mockPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { + return m.mockGetNextPeer() +} + +func TestClassBasedPeerSelector_makeClassBasedPeerSelector(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{}, + toleranceFactor: 3, + }, + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{}, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{}, + toleranceFactor: 10, + }, + } + + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // The selectors should be sorted by priority + require.Equal(t, 3, len(cps.peerSelectors)) + require.Equal(t, network.PeersPhonebookRelays, cps.peerSelectors[0].peerClass) + require.Equal(t, network.PeersConnectedOut, cps.peerSelectors[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, cps.peerSelectors[2].peerClass) +} + +func TestClassBasedPeerSelector_rankPeer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + + // Create a class based peer selector initially with the first wrapped peer selector not having the peer, + // second one having it, and a third one not having it + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + } + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // Peer is found in second selector, rank is within range for a block found + oldRank, newRank := cps.rankPeer(mockPeer, 50) + + require.Equal(t, 10, oldRank) + require.Equal(t, 50, newRank) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + + // Peer is found in second selector, rank is >= peerRankNoBlockForRound + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 1, cps.peerSelectors[1].downloadFailures) + + // We fail to find a block for round 3 more times, download failures should reflect that. + cps.rankPeer(mockPeer, peerRankNoBlockForRound) + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 3, cps.peerSelectors[1].downloadFailures) + + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 4, cps.peerSelectors[1].downloadFailures) + + // Now, feed peers that are not in any of the selectors - it should return -1, -1 + mockPeer2 := &peerSelectorPeer{ + peerClass: network.PeersConnectedIn, + } + + oldRank, newRank = cps.rankPeer(mockPeer2, 50) + require.Equal(t, -1, oldRank) + require.Equal(t, -1, newRank) + + // While this will match class, the selectors will not have it + mockPeer3 := &peerSelectorPeer{ + peerClass: network.PeersConnectedOut, + } + + oldRank, newRank = cps.rankPeer(mockPeer3, 50) + require.Equal(t, -1, oldRank) + require.Equal(t, -1, newRank) + + // Last sanity check, we should have zero download failures for the first and third selectors + require.Equal(t, 0, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) +} + +func TestClassBasedPeerSelector_peerDownloadDurationToRank(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{} + testDuration := 50 * time.Millisecond + + // Create a class based peer selector initially with the first wrapped peer selector not having the peer, + // second one having it, and a third one not having it + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + if psp == mockPeer && blockDownloadDuration == testDuration { + return peerRank0HighBlockTime + } + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + } + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // The peer is found in the second selector, so the rank should be peerRank0HighBlockTime + rank := cps.peerDownloadDurationToRank(mockPeer, testDuration) + require.Equal(t, peerRank0HighBlockTime, rank) + + // The peer is not found in any of the selectors, so the rank should be peerRankInvalidDownload + mockPeer2 := &peerSelectorPeer{} + + rank = cps.peerDownloadDurationToRank(mockPeer2, testDuration) + require.Equal(t, peerRankInvalidDownload, rank) +} + +func TestClassBasedPeerSelector_getNextPeer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + + // Create a class based peer selector initially with the first wrapped peer selector not having any peers, + // second one having a peer, and a third one not having any peers + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer, nil + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + }, + toleranceFactor: 3, + }, + } + + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + peerResult, err := cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + + // Update selector to not return any peers + wrappedPeerSelectors[1].peerSelector = mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, peerResult) + require.Equal(t, errPeerSelectorNoPeerPoolsAvailable, err) + + // Create a class based peer selector initially with all wrapped peer selectors having peers. + // The peers should always come from the first one repeatedly since rankings are not changed. + mockPeer = &peerSelectorPeer{ + peerClass: network.PeersConnectedOut, + } + mockPeer2 := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + mockPeer3 := &peerSelectorPeer{ + peerClass: network.PeersPhonebookArchivalNodes, + } + + wrappedPeerSelectors = []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer2, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer2 { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 10, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer3, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer3 { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + } + + cps = makeClassBasedPeerSelector(wrappedPeerSelectors) + + // We should always get the peer from the top priority selector since rankings are not updated/list is not re-sorted. + for i := 0; i < 10; i++ { + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + } + + // Okay, record enough download failures to disable the first selector + for i := 0; i < 4; i++ { + cps.rankPeer(mockPeer, peerRankNoBlockForRound) + } + + // Now, we should get the peer from the second selector + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer2, peerResult) + + // Sanity check the download failures for each selector + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) + + // Now, record download failures just up to the tolerance factor for the second selector + for i := 0; i < 10; i++ { + cps.rankPeer(mockPeer2, peerRankNoBlockForRound) + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer2, peerResult) + + // One more should push us to the third selector + cps.rankPeer(mockPeer2, peerRankNoBlockForRound) + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer3, peerResult) + + // Check of the download failures for each selector + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 11, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) + + // Now, record download failures just up to the tolerance factor for the third selector + for i := 0; i < 3; i++ { + cps.rankPeer(mockPeer3, peerRankNoBlockForRound) + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer3, peerResult) + + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 11, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 3, cps.peerSelectors[2].downloadFailures) + + // One more failure should reset ALL download failures (and grab a peer from the first selector) + cps.rankPeer(mockPeer3, peerRankNoBlockForRound) + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + + // Check of the download failures for each selector, should have been reset + require.Equal(t, 0, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) +} + +func TestClassBasedPeerSelector_integration(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockP1Peer := mockHTTPPeer{address: "p1"} + mockP2Peer := mockHTTPPeer{address: "p2"} + + mockP1WrappedPeer := &peerSelectorPeer{&mockP1Peer, network.PeersPhonebookRelays} + mockP2WrappedPeer := &peerSelectorPeer{&mockP2Peer, network.PeersPhonebookArchivalNodes} + + net := makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { + if len(options) > 0 { + switch options[0] { + case network.PeersPhonebookRelays: + return []network.Peer{&mockP1Peer} + case network.PeersPhonebookArchivalNodes: + return []network.Peer{&mockP2Peer} + default: + return []network.Peer{&mockP1Peer, &mockP2Peer} + } + } + return nil + }) + // Create a class based peer selector with a few wrapped peer selectors + cps := makeCatchpointPeerSelector(net).(*classBasedPeerSelector) + + // We should get the peer from the first priority selector, PeersPhonebookRelays + peerResult, err := cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + // Normal expected usage: rank the peer + durationRank := cps.peerDownloadDurationToRank(mockP1WrappedPeer, 500) + oldRank, newRank := cps.rankPeer(mockP1WrappedPeer, durationRank) + + require.Equal(t, 0, oldRank) + require.Equal(t, durationRank, newRank) + + // Let's simulate a few download failures (not enough to disable the selector) + for i := 0; i < 3; i++ { + expectedOldRank := newRank + peerResult, err = cps.getNextPeer() + + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + oldRank, newRank = cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound) + + require.Equal(t, expectedOldRank, oldRank) + // Should be increasing with no block penalties + require.True(t, newRank >= oldRank) + } + + // Sanity check, still should be the same peer (from phonebook selector) + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + // Rank the peer to follow normal usage + durationRank = cps.peerDownloadDurationToRank(mockP1WrappedPeer, 500) + expectedOldRank := newRank + oldRank, newRank = cps.rankPeer(mockP1WrappedPeer, durationRank) + + require.Equal(t, expectedOldRank, oldRank) + // Rank should not go up after successful download + require.True(t, newRank <= oldRank) + + // Now, let's simulate enough download failures to disable the first selector + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound) + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP2WrappedPeer, peerResult) + + // Normal expected usage: rank the peer + durationRank = cps.peerDownloadDurationToRank(mockP2WrappedPeer, 500) + oldRank, newRank = cps.rankPeer(mockP2WrappedPeer, durationRank) + + require.Equal(t, 0, oldRank) + require.Equal(t, durationRank, newRank) + + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) +} diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 4ceda8d42d..1485295581 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -88,7 +88,7 @@ type peerClass struct { peerClass network.PeerOption } -// the peersRetriever is a subset of the network.GossipNode used to ensure that we can create an instance of the peerSelector +// the peersRetriever is a subset of the network.GossipNode used to ensure that we can create an instance of the rankPooledPeerSelector // for testing purposes, providing just the above function. type peersRetriever interface { // Get a list of Peers we could potentially send a direct message to. @@ -109,14 +109,20 @@ type peerPool struct { peers []peerPoolEntry } -// peerSelector is a helper struct used to select the next peer to try and connect to +type peerSelector interface { + rankPeer(psp *peerSelectorPeer, rank int) (int, int) + peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) + getNextPeer() (psp *peerSelectorPeer, err error) +} + +// rankPooledPeerSelector is a helper struct used to select the next peer to try and connect to // for various catchup purposes. Unlike the underlying network GetPeers(), it allows the // client to provide feedback regarding the peer's performance, and to have the subsequent // query(s) take advantage of that intel. -type peerSelector struct { +type rankPooledPeerSelector struct { mu deadlock.Mutex net peersRetriever - // peerClasses is the list of peer classes we want to have in the peerSelector. + // peerClasses is the list of peer classes we want to have in the rankPooledPeerSelector. peerClasses []peerClass // pools is the list of peer pools, each pool contains a list of peers with the same rank. pools []peerPool @@ -284,9 +290,9 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera return bounded } -// makePeerSelector creates a peerSelector, given a peersRetriever and peerClass array. -func makePeerSelector(net peersRetriever, initialPeersClasses []peerClass) *peerSelector { - selector := &peerSelector{ +// makeRankPooledPeerSelector creates a rankPooledPeerSelector, given a peersRetriever and peerClass array. +func makeRankPooledPeerSelector(net peersRetriever, initialPeersClasses []peerClass) *rankPooledPeerSelector { + selector := &rankPooledPeerSelector{ net: net, peerClasses: initialPeersClasses, } @@ -296,7 +302,7 @@ func makePeerSelector(net peersRetriever, initialPeersClasses []peerClass) *peer // getNextPeer returns the next peer. It randomally selects a peer from a pool that has // the lowest rank value. Given that the peers are grouped by their ranks, allow us to // prioritize peers based on their class and/or performance. -func (ps *peerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { +func (ps *rankPooledPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { ps.mu.Lock() defer ps.mu.Unlock() ps.refreshAvailablePeers() @@ -317,7 +323,7 @@ func (ps *peerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { // rankPeer ranks a given peer. // return the old value and the new updated value. // updated value could be different from the input rank. -func (ps *peerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { +func (ps *rankPooledPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { if psp == nil { return -1, -1 } @@ -384,7 +390,7 @@ func (ps *peerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { } // peerDownloadDurationToRank calculates the rank for a peer given a peer and the block download time. -func (ps *peerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { +func (ps *rankPooledPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { ps.mu.Lock() defer ps.mu.Unlock() poolIdx, peerIdx := ps.findPeer(psp) @@ -409,7 +415,7 @@ func (ps *peerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockD // addToPool adds a given peer to the correct group. If no group exists for that peer's rank, // a new group is created. // The method return true if a new group was created ( suggesting that the pools list would need to be re-ordered ), or false otherwise. -func (ps *peerSelector) addToPool(peer network.Peer, rank int, class peerClass, peerHistory *historicStats) bool { +func (ps *rankPooledPeerSelector) addToPool(peer network.Peer, rank int, class peerClass, peerHistory *historicStats) bool { // see if we already have a list with that rank: for i, pool := range ps.pools { if pool.rank == rank { @@ -423,7 +429,7 @@ func (ps *peerSelector) addToPool(peer network.Peer, rank int, class peerClass, } // sort the pools array in an ascending order according to the rank of each pool. -func (ps *peerSelector) sort() { +func (ps *rankPooledPeerSelector) sort() { sort.SliceStable(ps.pools, func(i, j int) bool { return ps.pools[i].rank < ps.pools[j].rank }) @@ -443,7 +449,7 @@ func peerAddress(peer network.Peer) string { // refreshAvailablePeers reload the available peers from the network package, add new peers along with their // corresponding initial rank, and deletes peers that have been dropped by the network package. -func (ps *peerSelector) refreshAvailablePeers() { +func (ps *rankPooledPeerSelector) refreshAvailablePeers() { existingPeers := make(map[network.PeerOption]map[string]bool) for _, pool := range ps.pools { for _, localPeer := range pool.peers { @@ -501,7 +507,7 @@ func (ps *peerSelector) refreshAvailablePeers() { // findPeer look into the peer pool and find the given peer. // The method returns the pool and peer indices if a peer was found, or (-1, -1) otherwise. -func (ps *peerSelector) findPeer(psp *peerSelectorPeer) (poolIdx, peerIdx int) { +func (ps *rankPooledPeerSelector) findPeer(psp *peerSelectorPeer) (poolIdx, peerIdx int) { peerAddr := peerAddress(psp.Peer) if peerAddr == "" { return -1, -1 diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index aa8d348d43..7aa373d280 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -131,7 +131,7 @@ func TestPeerSelector_RankPeer(t *testing.T) { peers := []network.Peer{&mockHTTPPeer{address: "12345"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return peers }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, @@ -191,7 +191,7 @@ func TestPeerSelector_PeerDownloadRanking(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "1234"}, &mockHTTPPeer{address: "5678"}} peers2 := []network.Peer{&mockHTTPPeer{address: "abcd"}, &mockHTTPPeer{address: "efgh"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -240,7 +240,7 @@ func TestPeerSelector_FindMissingPeer(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return []network.Peer{} }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, @@ -258,7 +258,7 @@ func TestPeerSelector_HistoricData(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -332,7 +332,7 @@ func TestPeerSelector_PeersDownloadFailed(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -408,7 +408,7 @@ func TestPeerSelector_Penalty(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -469,7 +469,7 @@ func TestPeerSelector_PeerDownloadDurationToRank(t *testing.T) { peers3 := []network.Peer{&mockHTTPPeer{address: "c1"}, &mockHTTPPeer{address: "c2"}} peers4 := []network.Peer{&mockHTTPPeer{address: "d1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookRelays { @@ -574,7 +574,7 @@ func TestPeerSelector_ClassUpperBound(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -609,7 +609,7 @@ func TestPeerSelector_ClassLowerBound(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -639,7 +639,7 @@ func TestPeerSelector_EvictionAndUpgrade(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}} peers2 := []network.Peer{&mockHTTPPeer{address: "a1"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -677,7 +677,7 @@ func TestPeerSelector_RefreshAvailablePeers(t *testing.T) { // check new peers added to the pool p1 := mockHTTPPeer{address: "p1"} p2 := mockHTTPPeer{address: "p2"} - ps := peerSelector{ + ps := rankPooledPeerSelector{ peerClasses: []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, diff --git a/catchup/service.go b/catchup/service.go index 58fc3ae6b8..5c6609b236 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -38,10 +38,7 @@ import ( "github.com/algorand/go-algorand/util/execpool" ) -const catchupPeersForSync = 10 -const blockQueryPeerLimit = 10 - -// uncapParallelDownloadRate is a simple threshold to detect whether or not the node is caught up. +// uncapParallelDownloadRate is a simple threshold to detect whether the node is caught up. // If a block is downloaded in less than this duration, it's assumed that the node is not caught up // and allow the block downloader to start N=parallelBlocks concurrent fetches. const uncapParallelDownloadRate = time.Second @@ -76,7 +73,7 @@ type Ledger interface { WaitMem(r basics.Round) chan struct{} } -// Service represents the catchup service. Once started and until it is stopped, it ensures that the ledger is up to date with network. +// Service represents the catchup service. Once started and until it is stopped, it ensures that the ledger is up-to-date with network. type Service struct { // disableSyncRound, provided externally, is the first round we will _not_ fetch from the network // any round >= disableSyncRound will not be fetched. If set to 0, it will be disregarded. @@ -266,7 +263,7 @@ const errNoBlockForRoundThreshold = 5 // - If we couldn't fetch the block (e.g. if there are no peers available, or we've reached the catchupRetryLimit) // - If the block is already in the ledger (e.g. if agreement service has already written it) // - If the retrieval of the previous block was unsuccessful -func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCompleteChan chan struct{}, lookbackComplete chan struct{}, peerSelector *peerSelector) bool { +func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCompleteChan chan struct{}, lookbackComplete chan struct{}, peerSelector peerSelector) bool { // If sync-ing this round is not intended, don't fetch it if dontSyncRound := s.GetDisableSyncRound(); dontSyncRound != 0 && r >= basics.Round(dontSyncRound) { return false @@ -318,7 +315,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo block, cert, blockDownloadDuration, err := s.innerFetch(ctx, r, peer) if err != nil { - if err == errLedgerAlreadyHasBlock { + if errors.Is(err, errLedgerAlreadyHasBlock) { // ledger already has the block, no need to request this block. // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): the block is already in the ledger. The catchup is complete", r) @@ -329,7 +326,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo if errors.As(err, &nbfe) { failureRank = peerRankNoBlockForRound // remote peer doesn't have the block, try another peer - // quit if the the same peer peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times + // quit if the same peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times if s.followLatest { // back off between retries to allow time for the next block to appear; // this will provide 50s (catchupRetryLimit * followLatestBackoff) of @@ -427,7 +424,8 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo // if the context expired, just exit. return false } - if errNSBE, ok := err.(ledgercore.ErrNonSequentialBlockEval); ok && errNSBE.EvaluatorRound <= errNSBE.LatestRound { + var errNSBE ledgercore.ErrNonSequentialBlockEval + if errors.As(err, &errNSBE) && errNSBE.EvaluatorRound <= errNSBE.LatestRound { // the block was added to the ledger from elsewhere after fetching it here // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): after fetching the block, it is already in the ledger. The catchup is complete", r) @@ -442,16 +440,19 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo } if err != nil { - switch err.(type) { - case ledgercore.ErrNonSequentialBlockEval: + var errNonSequentialBlockEval ledgercore.ErrNonSequentialBlockEval + var blockInLedgerError ledgercore.BlockInLedgerError + var protocolErr protocol.Error + switch { + case errors.As(err, &errNonSequentialBlockEval): s.log.Infof("fetchAndWrite(%d): no need to re-evaluate historical block", r) return true - case ledgercore.BlockInLedgerError: + case errors.As(err, &blockInLedgerError): // the block was added to the ledger from elsewhere after fetching it here // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): after fetching the block, it is already in the ledger. The catchup is complete", r) return false - case protocol.Error: + case errors.As(err, &protocolErr): if !s.protocolErrorLogged { logging.Base().Errorf("fetchAndWrite(%v): unrecoverable protocol error detected: %v", r, err) s.protocolErrorLogged = true @@ -491,8 +492,8 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { } }() - peerSelector := createPeerSelector(s.net, s.cfg, true) - if _, err := peerSelector.getNextPeer(); err == errPeerSelectorNoPeerPoolsAvailable { + ps := createPeerSelector(s.net) + if _, err := ps.getNextPeer(); err != nil { s.log.Debugf("pipelinedFetch: was unable to obtain a peer to retrieve the block from") return } @@ -527,7 +528,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { go func(r basics.Round) { prev := s.ledger.WaitMem(r - 1) seed := s.ledger.WaitMem(r.SubSaturate(basics.Round(seedLookback))) - done <- s.fetchAndWrite(ctx, r, prev, seed, peerSelector) + done <- s.fetchAndWrite(ctx, r, prev, seed, ps) wg.Done() }(nextRound) @@ -751,9 +752,9 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy peerErrors := map[network.Peer]int{} blockHash := bookkeeping.BlockHash(cert.Proposal.BlockDigest) // semantic digest (i.e., hash of the block header), not byte-for-byte digest - peerSelector := createPeerSelector(s.net, s.cfg, false) + ps := createPeerSelector(s.net) for s.ledger.LastRound() < cert.Round { - psp, getPeerErr := peerSelector.getNextPeer() + psp, getPeerErr := ps.getNextPeer() if getPeerErr != nil { s.log.Debugf("fetchRound: was unable to obtain a peer to retrieve the block from") s.net.RequestConnectOutgoing(true, s.ctx.Done()) @@ -783,19 +784,19 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy time.Sleep(50 * time.Millisecond) } if count > errNoBlockForRoundThreshold*10 { - // for the low number of connected peers (like 2) the following scenatio is possible: + // for the low number of connected peers (like 2) the following scenario is possible: // - both peers do not have the block // - peer selector punishes one of the peers more than the other - // - the punoshed peer gets the block, and the less punished peer stucks. + // - the punished peer gets the block, and the less punished peer stucks. // It this case reset the peer selector to let it re-learn priorities. - peerSelector = createPeerSelector(s.net, s.cfg, false) + ps = createPeerSelector(s.net) } } peerErrors[peer]++ } // remote peer doesn't have the block, try another peer logging.Base().Warnf("fetchRound could not acquire block, fetcher errored out: %v", err) - peerSelector.rankPeer(psp, failureRank) + ps.rankPeer(psp, failureRank) continue } @@ -805,7 +806,7 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy } // Otherwise, fetcher gave us the wrong block logging.Base().Warnf("fetcher gave us bad/wrong block (for round %d): fetched hash %v; want hash %v", cert.Round, block.Hash(), blockHash) - peerSelector.rankPeer(psp, peerRankInvalidDownload) + ps.rankPeer(psp, peerRankInvalidDownload) // As a failsafe, if the cert we fetched is valid but for the wrong block, panic as loudly as possible if cert.Round == fetchedCert.Round && @@ -866,38 +867,33 @@ func (s *Service) roundIsNotSupported(nextRound basics.Round) bool { return true } -func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch bool) *peerSelector { - var peerClasses []peerClass - if pipelineFetch { - if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } - } - } else { - if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } - } +func createPeerSelector(net network.GossipNode) peerSelector { + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}), + toleranceFactor: 10, + }, + { + peerClass: network.PeersConnectedIn, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedIn}}), + toleranceFactor: 3, + }, } - return makePeerSelector(net, peerClasses) + + return makeClassBasedPeerSelector(wrappedPeerSelectors) } diff --git a/catchup/service_test.go b/catchup/service_test.go index 8deb692b0d..045a0438f2 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -958,102 +958,23 @@ func TestCatchupUnmatchedCertificate(t *testing.T) { func TestCreatePeerSelector(t *testing.T) { partitiontest.PartitionTest(t) - // Make Service - cfg := defaultConfig + s := MakeService(logging.Base(), defaultConfig, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) + ps := createPeerSelector(s.net) + + cps, ok := ps.(*classBasedPeerSelector) + require.True(t, ok) + + require.Equal(t, 4, len(cps.peerSelectors)) + + require.Equal(t, network.PeersConnectedOut, cps.peerSelectors[0].peerClass) + require.Equal(t, network.PeersPhonebookRelays, cps.peerSelectors[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, cps.peerSelectors[2].peerClass) + require.Equal(t, network.PeersConnectedIn, cps.peerSelectors[3].peerClass) - // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = true - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = true - s := MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps := createPeerSelector(s.net, s.cfg, true) - - require.Equal(t, 4, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) - - // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = true - cfg.NetAddress = "" - cfg.EnableGossipService = true - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, true) - - require.Equal(t, 3, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - - // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = true - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = false - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, true) - - require.Equal(t, 3, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - - // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = false - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = true - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 4, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - - // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = false - cfg.NetAddress = "" - cfg.EnableGossipService = true - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 3, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - - // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = false - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = false - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 3, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + require.Equal(t, 3, cps.peerSelectors[0].toleranceFactor) + require.Equal(t, 3, cps.peerSelectors[1].toleranceFactor) + require.Equal(t, 10, cps.peerSelectors[2].toleranceFactor) + require.Equal(t, 3, cps.peerSelectors[3].toleranceFactor) } func TestServiceStartStop(t *testing.T) { From 787f758f97985d1cf72a3f4d18a24c16499234e8 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:00:47 -0500 Subject: [PATCH 083/218] Network: Archival node DNS Resolution (#5940) --- catchup/peerSelector.go | 4 +-- config/localTemplate.go | 1 + network/gossipNode.go | 2 -- network/phonebook.go | 6 ++-- network/phonebook_test.go | 6 ++-- network/wsNetwork.go | 60 ++++++++++++++++++--------------------- network/wsNetwork_test.go | 27 +++++++++--------- 7 files changed, 51 insertions(+), 55 deletions(-) diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 1485295581..05556bb24b 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -29,12 +29,12 @@ import ( ) const ( - // peerRankInitialFirstPriority is the high-priority peers group ( typically, archivers ) + // peerRankInitialFirstPriority is the high-priority peers group peerRankInitialFirstPriority = 0 peerRank0LowBlockTime = 1 peerRank0HighBlockTime = 199 - // peerRankInitialSecondPriority is the second priority peers group ( typically, relays ) + // peerRankInitialSecondPriority is the second priority peers group peerRankInitialSecondPriority = 200 peerRank1LowBlockTime = 201 peerRank1HighBlockTime = 399 diff --git a/config/localTemplate.go b/config/localTemplate.go index 618facfd29..ce4294de01 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -171,6 +171,7 @@ type Local struct { RestWriteTimeoutSeconds int `version[4]:"120"` // DNSBootstrapID specifies the names of a set of DNS SRV records that identify the set of nodes available to connect to. + // This is applicable to both relay and archival nodes - they are assumed to use the same DNSBootstrapID today. // When resolving the bootstrap ID will be replaced by the genesis block's network name. This string uses a URL // parsing library and supports optional backup and dedup parameters. 'backup' is used to provide a second DNS entry to use // in case the primary is unavailable. dedup is intended to be used to deduplicate SRV records returned from the primary diff --git a/network/gossipNode.go b/network/gossipNode.go index 7a916fda36..3ac5cc7df0 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -42,8 +42,6 @@ const ( PeersPhonebookRelays PeerOption = iota // PeersPhonebookArchivalNodes specifies all archival nodes (relay or p2p) PeersPhonebookArchivalNodes PeerOption = iota - // PeersPhonebookArchivers specifies all archivers in the phonebook - PeersPhonebookArchivers PeerOption = iota ) // GossipNode represents a node in the gossip network diff --git a/network/phonebook.go b/network/phonebook.go index 3f196e0605..0c431fd2f3 100644 --- a/network/phonebook.go +++ b/network/phonebook.go @@ -30,7 +30,7 @@ import ( const getAllAddresses = math.MaxInt32 // PhoneBookEntryRoles defines the roles that a single entry on the phonebook can take. -// currently, we have two roles : relay role and archiver role, which are mutually exclusive. +// currently, we have two roles : relay role and archival role, which are mutually exclusive. // //msgp:ignore PhoneBookEntryRoles type PhoneBookEntryRoles int @@ -39,8 +39,8 @@ type PhoneBookEntryRoles int // or via a configuration file. const PhoneBookEntryRelayRole = 1 -// PhoneBookEntryArchiverRole used for all the archivers that are provided via the archive SRV record. -const PhoneBookEntryArchiverRole = 2 +// PhoneBookEntryArchivalRole used for all the archival nodes that are provided via the archive SRV record. +const PhoneBookEntryArchivalRole = 2 // Phonebook stores or looks up addresses of nodes we might contact type Phonebook interface { diff --git a/network/phonebook_test.go b/network/phonebook_test.go index 36365c5916..2643e722ea 100644 --- a/network/phonebook_test.go +++ b/network/phonebook_test.go @@ -346,11 +346,11 @@ func TestPhonebookRoles(t *testing.T) { ph := MakePhonebook(1, 1).(*phonebookImpl) ph.ReplacePeerList(relaysSet, "default", PhoneBookEntryRelayRole) - ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchiverRole) + ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchivalRole) require.Equal(t, len(relaysSet)+len(archiverSet), len(ph.data)) require.Equal(t, len(relaysSet)+len(archiverSet), ph.Length()) - for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} { + for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchivalRole} { for k := 0; k < 100; k++ { for l := 0; l < 3; l++ { entries := ph.GetAddresses(l, role) @@ -358,7 +358,7 @@ func TestPhonebookRoles(t *testing.T) { for _, entry := range entries { require.Contains(t, entry, "relay") } - } else if role == PhoneBookEntryArchiverRole { + } else if role == PhoneBookEntryArchivalRole { for _, entry := range entries { require.Contains(t, entry, "archiver") } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 92a02976b4..295bd71155 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -548,15 +548,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { } case PeersPhonebookArchivalNodes: var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) - for _, addr := range addrs { - peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) - outPeers = append(outPeers, &peerCore) - } - case PeersPhonebookArchivers: - // return copy of phonebook, which probably also contains peers we're connected to, but if it doesn't maybe we shouldn't be making new connections to those peers (because they disappeared from the directory) - var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchiverRole) + addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchivalRole) for _, addr := range addrs { peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) outPeers = append(outPeers, &peerCore) @@ -1607,15 +1599,17 @@ func (wn *WebsocketNetwork) refreshRelayArchivePhonebookAddresses() { dnsBootstrapArray := wn.config.DNSBootstrapArray(wn.NetworkID) for _, dnsBootstrap := range dnsBootstrapArray { - primaryRelayAddrs, primaryArchiveAddrs := wn.getDNSAddrs(dnsBootstrap.PrimarySRVBootstrap) + primaryRelayAddrs, primaryArchivalAddrs := wn.getDNSAddrs(dnsBootstrap.PrimarySRVBootstrap) if dnsBootstrap.BackupSRVBootstrap != "" { - backupRelayAddrs, backupArchiveAddrs := wn.getDNSAddrs(dnsBootstrap.BackupSRVBootstrap) - dedupedRelayAddresses := wn.mergePrimarySecondaryRelayAddressSlices(wn.NetworkID, primaryRelayAddrs, + backupRelayAddrs, backupArchivalAddrs := wn.getDNSAddrs(dnsBootstrap.BackupSRVBootstrap) + dedupedRelayAddresses := wn.mergePrimarySecondaryAddressSlices(primaryRelayAddrs, backupRelayAddrs, dnsBootstrap.DedupExp) - wn.updatePhonebookAddresses(dedupedRelayAddresses, append(primaryArchiveAddrs, backupArchiveAddrs...)) + dedupedArchivalAddresses := wn.mergePrimarySecondaryAddressSlices(primaryArchivalAddrs, + backupArchivalAddrs, dnsBootstrap.DedupExp) + wn.updatePhonebookAddresses(dedupedRelayAddresses, dedupedArchivalAddresses) } else { - wn.updatePhonebookAddresses(primaryRelayAddrs, primaryArchiveAddrs) + wn.updatePhonebookAddresses(primaryRelayAddrs, primaryArchivalAddrs) } } } @@ -1628,7 +1622,9 @@ func (wn *WebsocketNetwork) updatePhonebookAddresses(relayAddrs []string, archiv wn.log.Infof("got no relay DNS addrs for network %s", wn.NetworkID) } if len(archiveAddrs) > 0 { - wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchiverRole) + wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchivalRole) + } else { + wn.log.Infof("got no archive DNS addrs for network %s", wn.NetworkID) } } @@ -1846,46 +1842,46 @@ func (wn *WebsocketNetwork) prioWeightRefresh() { } } -// This logic assumes that the relay address suffixes +// This logic assumes that the address suffixes // correspond to the primary/backup network conventions. If this proves to be false, i.e. one network's // suffix is a substring of another network's suffix, then duplicates can end up in the merged slice. -func (wn *WebsocketNetwork) mergePrimarySecondaryRelayAddressSlices(network protocol.NetworkID, - primaryRelayAddresses []string, secondaryRelayAddresses []string, dedupExp *regexp.Regexp) (dedupedRelayAddresses []string) { +func (wn *WebsocketNetwork) mergePrimarySecondaryAddressSlices( + primaryAddresses []string, secondaryAddresses []string, dedupExp *regexp.Regexp) (dedupedAddresses []string) { if dedupExp == nil { // No expression provided, so just append the slices without deduping - return append(primaryRelayAddresses, secondaryRelayAddresses...) + return append(primaryAddresses, secondaryAddresses...) } - var relayAddressPrefixToValue = make(map[string]string, 2*len(primaryRelayAddresses)) + var addressPrefixToValue = make(map[string]string, 2*len(primaryAddresses)) - for _, pra := range primaryRelayAddresses { + for _, pra := range primaryAddresses { var normalizedPra = strings.ToLower(pra) var pfxKey = dedupExp.ReplaceAllString(normalizedPra, "") - if _, exists := relayAddressPrefixToValue[pfxKey]; !exists { - relayAddressPrefixToValue[pfxKey] = normalizedPra + if _, exists := addressPrefixToValue[pfxKey]; !exists { + addressPrefixToValue[pfxKey] = normalizedPra } } - for _, sra := range secondaryRelayAddresses { + for _, sra := range secondaryAddresses { var normalizedSra = strings.ToLower(sra) var pfxKey = dedupExp.ReplaceAllString(normalizedSra, "") - if _, exists := relayAddressPrefixToValue[pfxKey]; !exists { - relayAddressPrefixToValue[pfxKey] = normalizedSra + if _, exists := addressPrefixToValue[pfxKey]; !exists { + addressPrefixToValue[pfxKey] = normalizedSra } } - dedupedRelayAddresses = make([]string, 0, len(relayAddressPrefixToValue)) - for _, value := range relayAddressPrefixToValue { - dedupedRelayAddresses = append(dedupedRelayAddresses, value) + dedupedAddresses = make([]string, 0, len(addressPrefixToValue)) + for _, value := range addressPrefixToValue { + dedupedAddresses = append(dedupedAddresses, value) } return } -func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archiverAddresses []string) { +func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archivalAddresses []string) { var err error relaysAddresses, err = wn.resolveSRVRecords(wn.ctx, "algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { @@ -1896,13 +1892,13 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses [] relaysAddresses = nil } - archiverAddresses, err = wn.resolveSRVRecords(wn.ctx, "archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + archivalAddresses, err = wn.resolveSRVRecords(wn.ctx, "archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { // only log this warning on testnet or devnet if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) } - archiverAddresses = nil + archivalAddresses = nil } return } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index ef5769bcff..8daf4d196f 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1183,6 +1183,9 @@ func TestGetPeers(t *testing.T) { phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", PhoneBookEntryRelayRole) + // A few for archival node roles + phbMulti.ReplacePeerList([]string{"d", "e", "f"}, "ph", PhoneBookEntryArchivalRole) + //addrB, _ := netB.Address() // A has only an inbound connection from B @@ -1206,14 +1209,13 @@ func TestGetPeers(t *testing.T) { sort.Strings(expectAddrs) assert.Equal(t, expectAddrs, peerAddrs) - // For now, PeersPhonebookArchivalNodes and PeersPhonebookRelays will return the same set of nodes bPeers2 := netB.GetPeers(PeersPhonebookArchivalNodes) peerAddrs2 := make([]string, len(bPeers2)) for pi2, peer2 := range bPeers2 { peerAddrs2[pi2] = peer2.(HTTPPeer).GetAddress() } sort.Strings(peerAddrs2) - assert.Equal(t, expectAddrs, peerAddrs2) + assert.Equal(t, []string{"d", "e", "f"}, peerAddrs2) } @@ -4176,7 +4178,7 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { relayPeers := netA.GetPeers(PeersPhonebookRelays) assert.Equal(t, 0, len(relayPeers)) - archivePeers := netA.GetPeers(PeersPhonebookArchivers) + archivePeers := netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, 0, len(archivePeers)) netA.refreshRelayArchivePhonebookAddresses() @@ -4191,17 +4193,16 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) - // TODO: For the time being, we do not dedup resolved archive nodes - assert.Equal(t, len(primaryArchiveResolvedRecords)+len(secondaryArchiveResolvedRecords), len(archivePeers)) + assert.Equal(t, 3, len(archivePeers)) archiveAddrs := make([]string, 0, len(archivePeers)) for _, peer := range archivePeers { archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) } - assert.ElementsMatch(t, append(primaryArchiveResolvedRecords, secondaryArchiveResolvedRecords...), archiveAddrs) + assert.ElementsMatch(t, primaryArchiveResolvedRecords, archiveAddrs) }) } @@ -4219,7 +4220,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { relayPeers := netA.GetPeers(PeersPhonebookRelays) assert.Equal(t, 0, len(relayPeers)) - archivePeers := netA.GetPeers(PeersPhonebookArchivers) + archivePeers := netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, 0, len(archivePeers)) domainGen := rapidgen.Domain() @@ -4248,7 +4249,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, dedupedRelayDomains, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, len(dedupedArchiveDomains), len(archivePeers)) archiveAddrs := make([]string, 0, len(archivePeers)) @@ -4288,7 +4289,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, dedupedRelayDomains, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, len(dedupedArchiveDomains), len(archivePeers)) archiveAddrs = nil @@ -4349,7 +4350,7 @@ func TestMergePrimarySecondaryRelayAddressListsMinOverlap(t *testing.T) { primaryRelayAddresses := domainsGen.Draw(t1, "primaryRelayAddresses") secondaryRelayAddresses := domainsGen.Draw(t1, "secondaryRelayAddresses") - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(protocol.NetworkID(network), + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, dedupExp) expectedRelayAddresses := removeDuplicateStr(append(primaryRelayAddresses, secondaryRelayAddresses...), true) @@ -4402,7 +4403,7 @@ func TestMergePrimarySecondaryRelayAddressListsPartialOverlap(t *testing.T) { } secondaryRelayAddresses = append(secondaryRelayAddresses, extraSecondaryRelayAddresses...) - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(network, + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, dedupExp) // We expect the primary addresses to take precedence over a "matching" secondary address, extra non-duplicate @@ -4445,7 +4446,7 @@ func TestMergePrimarySecondaryRelayAddressListsNoDedupExp(t *testing.T) { generatedSecondaryRelayAddresses := secondaryDomainsGen.Draw(t1, "secondaryRelayAddresses") secondaryRelayAddresses = append(secondaryRelayAddresses, generatedSecondaryRelayAddresses...) - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(protocol.NetworkID(network), + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, nil) // We expect non deduplication, so all addresses _should_ be present (note that no lower casing happens either) From b0df43fc824a5f4d73d9fa546babe8acca5ac0cf Mon Sep 17 00:00:00 2001 From: John Lee Date: Mon, 4 Mar 2024 15:35:11 -0500 Subject: [PATCH 084/218] Bump version in buildnumber.dat --- buildnumber.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildnumber.dat b/buildnumber.dat index 573541ac97..d00491fd7e 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -0 +1 From 3905154043ab5e887832f16094f6abc96b735253 Mon Sep 17 00:00:00 2001 From: John Lee Date: Mon, 4 Mar 2024 18:31:37 -0500 Subject: [PATCH 085/218] CICD: fix package signing issues (#5934) (#81) --- package-deploy.yaml | 1 - .../mule/common/ensure_centos8_image.sh | 17 ++++++++ scripts/release/mule/deploy/deb/deploy.sh | 25 +++-------- scripts/release/mule/deploy/docker/docker.sh | 6 +-- .../releases_page/generate_releases_page.sh | 9 +--- scripts/release/mule/deploy/rpm/deploy.sh | 42 ++++++++++--------- scripts/release/mule/sign/sign.sh | 24 ++++++----- test/muleCI/mule.yaml | 35 ---------------- 8 files changed, 64 insertions(+), 95 deletions(-) create mode 100755 scripts/release/mule/common/ensure_centos8_image.sh diff --git a/package-deploy.yaml b/package-deploy.yaml index 8daf262acc..9b67a2fe6c 100644 --- a/package-deploy.yaml +++ b/package-deploy.yaml @@ -56,7 +56,6 @@ agents: - NETWORK=$NETWORK - NO_DEPLOY=$NO_DEPLOY - PACKAGES_DIR=$PACKAGES_DIR - - S3_SOURCE=$S3_SOURCE - STAGING=$STAGING - VERSION=$VERSION volumes: diff --git a/scripts/release/mule/common/ensure_centos8_image.sh b/scripts/release/mule/common/ensure_centos8_image.sh new file mode 100755 index 0000000000..1ebd3475fe --- /dev/null +++ b/scripts/release/mule/common/ensure_centos8_image.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -exo pipefail + +# Ensure the centos8 docker image is built and available + +DOCKER_IMAGE="algorand/go-algorand-ci-linux-centos8:amd64-$(sha1sum scripts/configure_dev-deps.sh | cut -f1 -d' ')" +MATCH=${DOCKER_IMAGE/:*/} + +echo "Checking for RPM image" +if docker images $DOCKER_IMAGE | grep -qs $MATCH > /dev/null 2>&1; then + echo "Image exists" +else + echo "RPM image doesn't exist, building" + docker build --platform=linux/amd64 --build-arg ARCH=amd64 \ + --build-arg GOLANG_VERSION=$(./scripts/get_golang_version.sh) -t $DOCKER_IMAGE -f docker/build/cicd.centos8.Dockerfile . +fi diff --git a/scripts/release/mule/deploy/deb/deploy.sh b/scripts/release/mule/deploy/deb/deploy.sh index c9c4b4b6c4..2584b84123 100755 --- a/scripts/release/mule/deploy/deb/deploy.sh +++ b/scripts/release/mule/deploy/deb/deploy.sh @@ -2,28 +2,13 @@ set -ex -if [ -z "$NETWORK" ] -then - echo "[$0] Network is a required parameter." - exit 1 -fi - -if [ -z "$STAGING" ] -then - echo "[$0] Staging is a required parameter." - exit 1 -fi - -CHANNEL=$("./scripts/release/mule/common/get_channel.sh" "$NETWORK") +CHANNEL=${CHANNEL:-$("./scripts/release/mule/common/get_channel.sh" "$NETWORK")} VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} +PACKAGES_DIR=${PACKAGES_DIR:-~/packages} +SNAPSHOT=${SNAPSHOT:-"${CHANNEL}-${VERSION}"} -if [ -z "$SNAPSHOT" ] -then - SNAPSHOT="$CHANNEL-$VERSION" -fi - -PACKAGES_DIR=/root/packages -mkdir -p /root/packages +mkdir -p $PACKAGES_DIR +rm -f $PACKAGES_DIR/*.deb aptly mirror update stable aptly mirror update beta diff --git a/scripts/release/mule/deploy/docker/docker.sh b/scripts/release/mule/deploy/docker/docker.sh index ee0c55fe0c..093922fd3d 100755 --- a/scripts/release/mule/deploy/docker/docker.sh +++ b/scripts/release/mule/deploy/docker/docker.sh @@ -13,9 +13,9 @@ if [ -z "$NETWORK" ] || [ -z "$VERSION" ]; then exit 1 fi -if [[ ! "$NETWORK" =~ ^mainnet$|^testnet$|^betanet$|^alphanet$ ]] +if [[ ! "$NETWORK" =~ ^mainnet$|^testnet$|^betanet$ ]] then - echo "[$0] Network values must be either \`mainnet\`, \`testnet\`, \`betanet\`, or \`alphanet\`." + echo "[$0] Network values must be either \`mainnet\`, \`testnet\`, or \`betanet\`." exit 1 fi @@ -28,7 +28,7 @@ then # Build and push testnet. ./build_releases.sh --tagname "$VERSION" --network testnet --cached -elif [ "$NETWORK" = betanet ] || [ "$NETWORK" = alphanet ] +elif [ "$NETWORK" = betanet ] then ./build_releases.sh --tagname "$VERSION" --network "$NETWORK" fi diff --git a/scripts/release/mule/deploy/releases_page/generate_releases_page.sh b/scripts/release/mule/deploy/releases_page/generate_releases_page.sh index 5b6a488ce2..75df10d524 100755 --- a/scripts/release/mule/deploy/releases_page/generate_releases_page.sh +++ b/scripts/release/mule/deploy/releases_page/generate_releases_page.sh @@ -8,13 +8,8 @@ set -ex -if [ -z "$NETWORK" ] || [ -z "$VERSION" ] -then - echo "[$0] Network and version are required parameters." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} +VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} cd scripts/release/mule/deploy/releases_page diff --git a/scripts/release/mule/deploy/rpm/deploy.sh b/scripts/release/mule/deploy/rpm/deploy.sh index f660f1d01b..b96cccd6cb 100755 --- a/scripts/release/mule/deploy/rpm/deploy.sh +++ b/scripts/release/mule/deploy/rpm/deploy.sh @@ -7,28 +7,25 @@ echo date "+build_release begin DEPLOY rpm stage %Y%m%d_%H%M%S" echo -if [ -z "$NETWORK" ]; then - echo "[$0] NETWORK is missing." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} 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"} +PACKAGES_DIR=${PACKAGES_DIR:-"tmp"} if [ -n "$S3_SOURCE" ] then PREFIX="$S3_SOURCE/$CHANNEL/$VERSION" 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 + aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.aarch64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.aarch64.rpm" $PACKAGES_DIR 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 + aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.aarch64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.aarch64.rpm" $PACKAGES_DIR fi else cp "$PACKAGES_DIR"/*"$VERSION"*.rpm /root @@ -56,11 +53,7 @@ cat << EOF > .rpmmacros EOF mkdir rpmrepo -for rpm in $(ls *"$VERSION"*.rpm) -do - rpmsign --addsign "$rpm" - cp -p "$rpm" rpmrepo -done +mv -f *"$VERSION"*.rpm rpmrepo createrepo --database rpmrepo rm -f rpmrepo/repodata/repomd.xml.asc @@ -72,8 +65,19 @@ 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' + if [ -n "$S3_SOURCE" ]; then + # if S3_SOURCE exists, we copied files from s3 + echo "Copy signatures from s3 staging to s3 releases" + aws s3 sync s3://algorand-staging/releases/$CHANNEL/ s3://algorand-releases/rpm/sigs/$CHANNEL/ --exclude='*' --include='*.rpm.sig' + + else + # We are working with files locally + popd + echo "Copy local signatures to s3 releases" + aws s3 sync "$PACKAGES_DIR" "s3://algorand-releases/rpm/sigs/$CHANNEL/" --exclude='*' --include='*.rpm.sig' + fi fi echo diff --git a/scripts/release/mule/sign/sign.sh b/scripts/release/mule/sign/sign.sh index e08e2d52d3..89baedb9ce 100755 --- a/scripts/release/mule/sign/sign.sh +++ b/scripts/release/mule/sign/sign.sh @@ -10,18 +10,13 @@ echo date "+build_release begin SIGN stage %Y%m%d_%H%M%S" echo -if [ -z "$NETWORK" ]; then - echo "[$0] NETWORK is missing." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} PKG_DIR="./tmp/node_pkgs" SIGNING_KEY_ADDR=dev@algorand.com OS_TYPE=$(./scripts/release/mule/common/ostype.sh) -ARCHS=(amd64 arm arm64) -ARCH_BITS=(x86_64 armv7l aarch64) +ARCHS=(amd64 arm64) +ARCH_BITS=(x86_64 aarch64) # Note that we don't want to use $GNUPGHOME here because that is a documented env var for the gnupg # project and if it's set in the environment mule will automatically pick it up, which could have # unintended consequences and be hard to debug. @@ -40,6 +35,14 @@ then find "$GPG_DIR" -type f -exec chmod 600 {} \; fi +pushd /root +cat << EOF > .rpmmacros +%_gpg_name Algorand RPM +%__gpg /usr/bin/gpg2 +%__gpg_check_password_cmd true +EOF +popd + # Note that when downloading from the cloud that we'll get all packages for all architectures. if [ -n "$S3_SOURCE" ] then @@ -87,13 +90,14 @@ for os in "${OS_TYPES[@]}"; do for file in *.rpm do + rpmsign --addsign "$file" gpg -u rpm@algorand.com --detach-sign "$file" done HASHFILE="hashes_${CHANNEL}_${os}_${arch}_${VERSION}" md5sum *.tar.gz *.deb *.rpm >> "$HASHFILE" - shasum -a 256 *.tar.gz *.deb *.rpm >> "$HASHFILE" - shasum -a 512 *.tar.gz *.deb *.rpm >> "$HASHFILE" + sha256sum *.tar.gz *.deb *.rpm >> "$HASHFILE" + sha512sum *.tar.gz *.deb *.rpm >> "$HASHFILE" gpg -u "$SIGNING_KEY_ADDR" --detach-sign "$HASHFILE" gpg -u "$SIGNING_KEY_ADDR" --clearsign "$HASHFILE" diff --git a/test/muleCI/mule.yaml b/test/muleCI/mule.yaml index cb75187db4..afa73ae2c1 100644 --- a/test/muleCI/mule.yaml +++ b/test/muleCI/mule.yaml @@ -61,22 +61,6 @@ agents: - GOLANG_VERSION=`./scripts/get_golang_version.sh` - ARCH=arm64v8 - GOARCH=arm64 - - name: cicd.ubuntu.arm - dockerFilePath: docker/build/cicd.ubuntu.Dockerfile - image: algorand/go-algorand-ci-linux - version: scripts/configure_dev-deps.sh - arch: arm32v7 - env: - - TRAVIS_BRANCH=${GIT_BRANCH} - - NETWORK=$NETWORK - - VERSION=$VERSION - - BUILD_NUMBER=$BUILD_NUMBER - - GOHOSTARCH=arm - - FULLVERSION=${FULLVERSION} - buildArgs: - - GOLANG_VERSION=`./scripts/get_golang_version.sh` - - ARCH=arm32v7 - - GOARCH=armv6l - name: docker-ubuntu dockerFilePath: docker/build/docker.ubuntu.Dockerfile image: algorand/go-algorand-docker-linux-ubuntu @@ -122,10 +106,6 @@ tasks: name: build.arm64 agent: cicd.ubuntu.arm64 target: ci-build - - task: docker.Make - name: build.arm - agent: cicd.ubuntu.arm - target: ci-build - task: docker.Make name: archive @@ -167,12 +147,6 @@ tasks: stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm64 globSpecs: - tmp/node_pkgs/**/* - - task: stash.Stash - name: linux-arm - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm - globSpecs: - - tmp/node_pkgs/**/* - task: stash.Stash name: packages bucketName: go-algorand-ci-cache @@ -193,10 +167,6 @@ tasks: name: darwin-amd64 bucketName: go-algorand-ci-cache stashId: ${JENKINS_JOB_CACHE_ID}/darwin-amd64 - - task: stash.Unstash - name: linux-arm - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm - task: stash.Unstash name: darwin-arm64 bucketName: go-algorand-ci-cache @@ -233,15 +203,10 @@ jobs: tasks: - docker.Make.build.arm64 - stash.Stash.linux-arm64 - build-linux-arm32: - tasks: - - docker.Make.build.arm - - stash.Stash.linux-arm package-all: tasks: - stash.Unstash.linux-amd64 - stash.Unstash.linux-arm64 - - stash.Unstash.linux-arm - stash.Unstash.darwin-arm64 - stash.Unstash.darwin-amd64 - docker.Make.deb.amd64 From 3e50c3b9102348fbdd651b34fa9f0d3bb4a188b6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 4 Mar 2024 20:52:54 -0500 Subject: [PATCH 086/218] Consider the current auth addr for each inner txn (#83) --- data/transactions/logic/eval.go | 21 ++++++++-------- data/transactions/logic/evalAppTxn_test.go | 28 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 210d7a3164..2bf6f2f272 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -5509,18 +5509,15 @@ func opItxnSubmit(cx *EvalContext) (err error) { parent = cx.currentTxID() } for itx := range cx.subtxns { - // The goal is to follow the same invariants used by the - // transaction pool. Namely that any transaction that makes it - // to Perform (which is equivalent to eval.applyTransaction) - // is authorized, and WellFormed. - txnErr := authorizedSender(cx, cx.subtxns[itx].Txn.Sender) - if txnErr != nil { - return txnErr - } + // The goal is to follow the same invariants used by the transaction + // pool. Namely that any transaction that makes it to Perform (which is + // equivalent to eval.applyTransaction) is WellFormed. Authorization + // must be checked later, to take state changes from earlier in the + // group into account. // Recall that WellFormed does not care about individual // transaction fees because of fee pooling. Checked above. - txnErr = cx.subtxns[itx].Txn.WellFormed(*cx.Specials, *cx.Proto) + txnErr := cx.subtxns[itx].Txn.WellFormed(*cx.Specials, *cx.Proto) if txnErr != nil { return txnErr } @@ -5639,7 +5636,11 @@ func opItxnSubmit(cx *EvalContext) (err error) { ep.Tracer.BeforeTxn(ep, i) } - err := cx.Ledger.Perform(i, ep) + err := authorizedSender(cx, ep.TxnGroup[i].Txn.Sender) + if err != nil { + return err + } + err = cx.Ledger.Perform(i, ep) if ep.Tracer != nil { ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, err) diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 9a6d0ebc41..562142fdec 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -375,6 +375,34 @@ func TestRekeyBack(t *testing.T) { }) } +// TestRekeyInnerGroup ensures that in an inner group, if an account is +// rekeyed, it can not be used (by the previously owning app) later in the +// group. +func TestRekeyInnerGroup(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rekeyAndUse := ` + itxn_begin + // pay 0 to the zero address, and rekey a junk addr + int pay; itxn_field TypeEnum + global ZeroAddress; byte 0x01; b|; itxn_field RekeyTo + itxn_next + // try to perform the same 0 pay, but fail because tx0 gave away control + int pay; itxn_field TypeEnum + itxn_submit + int 1 +` + + // v6 added inner rekey + TestLogicRange(t, 6, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + // fund the app account + ledger.NewAccount(basics.AppIndex(888).Address(), 1_000_000) + TestApp(t, rekeyAndUse, ep, "unauthorized AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVIOOBQA") + }) +} + func TestDefaultSender(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() From e7ee984cea957f2c59f344f0bb10f08bb58d8820 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 6 Mar 2024 13:06:10 -0500 Subject: [PATCH 087/218] agreement: update voteValidatedAt description in comment (#5945) --- agreement/actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agreement/actions.go b/agreement/actions.go index 9a6fa7138e..c174233103 100644 --- a/agreement/actions.go +++ b/agreement/actions.go @@ -232,7 +232,7 @@ type ensureAction struct { Payload proposal // the certificate proving commitment Certificate Certificate - // The time that the winning proposal-vote was validated for round credentialRoundLag back from the current one + // The time that the lowest proposal-vote was validated for `credentialRoundLag` rounds ago (R-credentialRoundLag). This may not have been the winning proposal, since we wait `credentialRoundLag` rounds to see if there was a better one. voteValidatedAt time.Duration // The dynamic filter timeout calculated for this round, even if not enabled, for reporting to telemetry. dynamicFilterTimeout time.Duration From 86ae7e63f0dc8fc5e8497c8f7e4f6a267ac3b0b9 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:08:44 -0500 Subject: [PATCH 088/218] Network: Support simple liveness check via http on gossip server port. (#5944) --- node/follower_node.go | 5 ++++ node/node.go | 5 ++++ rpcs/healthService.go | 41 ++++++++++++++++++++++++++++++ rpcs/healthService_test.go | 52 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 rpcs/healthService.go create mode 100644 rpcs/healthService_test.go diff --git a/node/follower_node.go b/node/follower_node.go index 277c48a24e..8483f14679 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -127,6 +127,11 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo } node.ledger.RegisterBlockListeners(blockListeners) + + if cfg.IsGossipServer() { + rpcs.MakeHealthService(node.net) + } + node.blockService = rpcs.MakeBlockService(node.log, cfg, node.ledger, p2pNode, node.genesisID) node.catchupBlockAuth = blockAuthenticatorImpl{Ledger: node.ledger, AsyncVoteVerifier: agreement.MakeAsyncVoteVerifier(node.lowPriorityCryptoVerificationPool)} node.catchupService = catchup.MakeService(node.log, node.config, p2pNode, node.ledger, node.catchupBlockAuth, make(chan catchup.PendingUnmatchedCertificate), node.lowPriorityCryptoVerificationPool) diff --git a/node/node.go b/node/node.go index f59bd67dab..53c6c492a9 100644 --- a/node/node.go +++ b/node/node.go @@ -251,6 +251,11 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd return nil, err } + // The health service registers itself with the network + if cfg.IsGossipServer() { + rpcs.MakeHealthService(node.net) + } + node.blockService = rpcs.MakeBlockService(node.log, cfg, node.ledger, p2pNode, node.genesisID) node.ledgerService = rpcs.MakeLedgerService(cfg, node.ledger, p2pNode, node.genesisID) rpcs.RegisterTxService(node.transactionPool, p2pNode, node.genesisID, cfg.TxPoolSize, cfg.TxSyncServeResponseSize) diff --git a/rpcs/healthService.go b/rpcs/healthService.go new file mode 100644 index 0000000000..d3121d8bd2 --- /dev/null +++ b/rpcs/healthService.go @@ -0,0 +1,41 @@ +// Copyright (C) 2019-2024 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 rpcs + +import ( + "github.com/algorand/go-algorand/network" + "net/http" +) + +// HealthServiceStatusPath is the path to register HealthService as a handler for when using gorilla/mux +const HealthServiceStatusPath = "/status" + +// HealthService is a service that provides health information endpoints for the node +type HealthService struct{} + +// MakeHealthService creates a new HealthService and registers it with the provided network if enabled +func MakeHealthService(net network.GossipNode) HealthService { + service := HealthService{} + + net.RegisterHTTPHandler(HealthServiceStatusPath, service) + + return service +} + +func (h HealthService) ServeHTTP(writer http.ResponseWriter, _ *http.Request) { + writer.WriteHeader(http.StatusOK) +} diff --git a/rpcs/healthService_test.go b/rpcs/healthService_test.go new file mode 100644 index 0000000000..9d0bb215c2 --- /dev/null +++ b/rpcs/healthService_test.go @@ -0,0 +1,52 @@ +// Copyright (C) 2019-2024 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 rpcs + +import ( + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "io" + "net/http" + "path" + "testing" +) + +func TestHealthService_ServeHTTP(t *testing.T) { + partitiontest.PartitionTest(t) + + nodeA := &basicRPCNode{} + nodeA.start() + defer nodeA.stop() + + _ = MakeHealthService(nodeA) + + parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + require.NoError(t, err) + + client := http.Client{} + + parsedURL.Path = path.Join(parsedURL.Path, HealthServiceStatusPath) + + response, err := client.Get(parsedURL.String()) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode) + bodyData, err := io.ReadAll(response.Body) + require.NoError(t, err) + require.Empty(t, bodyData) +} From 13e66ff9ba5073637f69f9dd4e5572f19b77e38c Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:41:17 -0500 Subject: [PATCH 089/218] build: upgrade to go 1.20.14 (#5949) --- Dockerfile | 2 +- scripts/get_golang_version.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 468d257360..c6802506af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 as builder -ARG GO_VERSION="1.20.7" +ARG GO_VERSION="1.20.14" ARG CHANNEL ARG URL diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh index 10bdb8630d..13dfca5bf1 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.7 +BUILD=1.20.14 MIN=1.20 GO_MOD_SUPPORT=1.20 From 58fea01ac356e8be51a7e5cda71b90a2e3b23a9e Mon Sep 17 00:00:00 2001 From: algo-devops-service <80971703+Algo-devops-service@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:48:44 -0500 Subject: [PATCH 090/218] go-algorand 3.23.0-beta Release PR (#5950) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gary <982483+gmalouf@users.noreply.github.com> Co-authored-by: John Jannotti Co-authored-by: nullun Co-authored-by: John Lee <64482439+algojohnlee@users.noreply.github.com> Co-authored-by: Halimao <1065621723@qq.com> Co-authored-by: John Lee Co-authored-by: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Co-authored-by: cce <51567+cce@users.noreply.github.com> Co-authored-by: ohill <145173879+ohill@users.noreply.github.com> --- Dockerfile | 2 +- agreement/actions.go | 2 +- buildnumber.dat | 2 +- catchup/catchpointService.go | 89 ++-- catchup/classBasedPeerSelector.go | 156 +++++++ catchup/classBasedPeerSelector_test.go | 498 +++++++++++++++++++++ catchup/peerSelector.go | 38 +- catchup/peerSelector_test.go | 91 ++-- catchup/service.go | 146 +++--- catchup/service_test.go | 153 +------ cmd/algokey/common.go | 16 + cmd/algokey/common_test.go | 39 ++ cmd/algokey/generate.go | 12 +- cmd/catchpointdump/net.go | 2 +- cmd/netgoal/generate.go | 3 + config/localTemplate.go | 21 +- config/local_defaults.go | 2 - config/version.go | 2 +- go.mod | 5 + go.sum | 10 + installer/config.json.example | 2 - ledger/ledger.go | 16 +- ledger/ledger_test.go | 64 +++ network/gossipNode.go | 2 - network/phonebook.go | 6 +- network/phonebook_test.go | 6 +- network/wsNetwork.go | 75 ++-- network/wsNetwork_test.go | 149 +++--- node/follower_node.go | 5 + node/node.go | 5 + rpcs/blockService.go | 32 +- rpcs/blockService_test.go | 156 +++---- rpcs/healthService.go | 41 ++ rpcs/healthService_test.go | 52 +++ scripts/get_golang_version.sh | 2 +- test/heapwatch/requirements.txt | 2 +- test/testdata/configs/config-v33.json | 2 - tools/network/bootstrap.go | 14 +- tools/network/bootstrap_test.go | 5 +- tools/network/telemetryURIUpdateService.go | 3 +- 40 files changed, 1290 insertions(+), 638 deletions(-) create mode 100644 catchup/classBasedPeerSelector.go create mode 100644 catchup/classBasedPeerSelector_test.go create mode 100644 cmd/algokey/common_test.go create mode 100644 rpcs/healthService.go create mode 100644 rpcs/healthService_test.go diff --git a/Dockerfile b/Dockerfile index 468d257360..c6802506af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 as builder -ARG GO_VERSION="1.20.7" +ARG GO_VERSION="1.20.14" ARG CHANNEL ARG URL diff --git a/agreement/actions.go b/agreement/actions.go index 9a6fa7138e..c174233103 100644 --- a/agreement/actions.go +++ b/agreement/actions.go @@ -232,7 +232,7 @@ type ensureAction struct { Payload proposal // the certificate proving commitment Certificate Certificate - // The time that the winning proposal-vote was validated for round credentialRoundLag back from the current one + // The time that the lowest proposal-vote was validated for `credentialRoundLag` rounds ago (R-credentialRoundLag). This may not have been the winning proposal, since we wait `credentialRoundLag` rounds to see if there was a better one. voteValidatedAt time.Duration // The dynamic filter timeout calculated for this round, even if not enabled, for reporting to telemetry. dynamicFilterTimeout time.Duration diff --git a/buildnumber.dat b/buildnumber.dat index d00491fd7e..573541ac97 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -1 +0 diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index f71a4209ae..3c11d0db73 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -18,6 +18,7 @@ package catchup import ( "context" + "errors" "fmt" "sync" "time" @@ -69,7 +70,7 @@ type CatchpointCatchupStats struct { type CatchpointCatchupService struct { // stats is the statistics object, updated async while downloading the ledger stats CatchpointCatchupStats - // statsMu synchronizes access to stats, as we could attempt to update it while querying for it's current state + // statsMu synchronizes access to stats, as we could attempt to update it while querying for its current state statsMu deadlock.Mutex node CatchpointCatchupNodeServices // ctx is the node cancellation context, used when the node is being stopped. @@ -98,7 +99,7 @@ type CatchpointCatchupService struct { abortCtx context.Context abortCtxFunc context.CancelFunc // blocksDownloadPeerSelector is the peer selector used for downloading blocks. - blocksDownloadPeerSelector *peerSelector + blocksDownloadPeerSelector peerSelector } // MakeResumedCatchpointCatchupService creates a catchpoint catchup service for a node that is already in catchpoint catchup mode @@ -280,51 +281,50 @@ func (cs *CatchpointCatchupService) processStageInactive() (err error) { } // processStageLedgerDownload is the second catchpoint catchup stage. It downloads the ledger. -func (cs *CatchpointCatchupService) processStageLedgerDownload() (err error) { +func (cs *CatchpointCatchupService) processStageLedgerDownload() error { cs.statsMu.Lock() label := cs.stats.CatchpointLabel cs.statsMu.Unlock() - round, _, err0 := ledgercore.ParseCatchpointLabel(label) + round, _, err := ledgercore.ParseCatchpointLabel(label) - if err0 != nil { - return cs.abort(fmt.Errorf("processStageLedgerDownload failed to parse label : %v", err0)) + if err != nil { + return cs.abort(fmt.Errorf("processStageLedgerDownload failed to parse label : %v", err)) } // download balances file. - peerSelector := cs.makeCatchpointPeerSelector() - ledgerFetcher := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) + lf := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) attemptsCount := 0 for { attemptsCount++ - err = cs.ledgerAccessor.ResetStagingBalances(cs.ctx, true) - if err != nil { + err0 := cs.ledgerAccessor.ResetStagingBalances(cs.ctx, true) + if err0 != nil { if cs.ctx.Err() != nil { return cs.stopOrAbort() } - return cs.abort(fmt.Errorf("processStageLedgerDownload failed to reset staging balances : %v", err)) + return cs.abort(fmt.Errorf("processStageLedgerDownload failed to reset staging balances : %v", err0)) } - psp, err := peerSelector.getNextPeer() - if err != nil { - err = fmt.Errorf("processStageLedgerDownload: catchpoint catchup was unable to obtain a list of peers to retrieve the catchpoint file from") - return cs.abort(err) + psp, err0 := cs.blocksDownloadPeerSelector.getNextPeer() + if err0 != nil { + err0 = fmt.Errorf("processStageLedgerDownload: catchpoint catchup was unable to obtain a list of peers to retrieve the catchpoint file from") + return cs.abort(err0) } peer := psp.Peer start := time.Now() - err = ledgerFetcher.downloadLedger(cs.ctx, peer, round) - if err == nil { + err0 = lf.downloadLedger(cs.ctx, peer, round) + if err0 == nil { cs.log.Infof("ledger downloaded in %d seconds", time.Since(start)/time.Second) start = time.Now() - err = cs.ledgerAccessor.BuildMerkleTrie(cs.ctx, cs.updateVerifiedCounts) - if err == nil { + err0 = cs.ledgerAccessor.BuildMerkleTrie(cs.ctx, cs.updateVerifiedCounts) + if err0 == nil { cs.log.Infof("built merkle trie in %d seconds", time.Since(start)/time.Second) break } // failed to build the merkle trie for the above catchpoint file. - peerSelector.rankPeer(psp, peerRankInvalidDownload) + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankInvalidDownload) } else { - peerSelector.rankPeer(psp, peerRankDownloadFailed) + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankDownloadFailed) } // instead of testing for err == cs.ctx.Err() , we'll check on the context itself. @@ -335,10 +335,10 @@ func (cs *CatchpointCatchupService) processStageLedgerDownload() (err error) { } if attemptsCount >= cs.config.CatchupLedgerDownloadRetryAttempts { - err = fmt.Errorf("processStageLedgerDownload: catchpoint catchup exceeded number of attempts to retrieve ledger") - return cs.abort(err) + err0 = fmt.Errorf("processStageLedgerDownload: catchpoint catchup exceeded number of attempts to retrieve ledger") + return cs.abort(err0) } - cs.log.Warnf("unable to download ledger : %v", err) + cs.log.Warnf("unable to download ledger : %v", err0) } err = cs.updateStage(ledger.CatchpointCatchupStateLatestBlockDownload) @@ -506,14 +506,14 @@ func lookbackForStateproofsSupport(topBlock *bookkeeping.Block) uint64 { return uint64(topBlock.Round().SubSaturate(lowestStateProofRound)) } -// processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against it's predecessor. +// processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against its predecessor. func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { topBlock, err := cs.ledgerAccessor.EnsureFirstBlock(cs.ctx) if err != nil { return cs.abort(fmt.Errorf("processStageBlocksDownload failed, unable to ensure first block : %v", err)) } - // pick the lookback with the greater of + // pick the lookback with the greatest of // either (MaxTxnLife+DeeperBlockHeaderHistory+CatchpointLookback) or MaxBalLookback // Explanation: // 1. catchpoint snapshots accounts at round X-CatchpointLookback @@ -531,13 +531,13 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { } // in case the effective lookback is going before our rounds count, trim it there. - // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback..MaxTxnLife) + // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback...MaxTxnLife) if lookback >= uint64(topBlock.Round()) { lookback = uint64(topBlock.Round() - 1) } cs.statsMu.Lock() - cs.stats.TotalBlocks = uint64(lookback) + cs.stats.TotalBlocks = lookback cs.stats.AcquiredBlocks = 0 cs.stats.VerifiedBlocks = 0 cs.statsMu.Unlock() @@ -558,8 +558,9 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { blk = &ledgerBlock cert = &ledgerCert } else { - switch err0.(type) { - case ledgercore.ErrNoEntry: + var errNoEntry ledgercore.ErrNoEntry + switch { + case errors.As(err0, &errNoEntry): // this is expected, ignore this one. default: cs.log.Warnf("processStageBlocksDownload encountered the following error when attempting to retrieve the block for round %d : %v", topBlock.Round()-basics.Round(blocksFetched), err0) @@ -658,7 +659,7 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { func (cs *CatchpointCatchupService) fetchBlock(round basics.Round, retryCount uint64) (blk *bookkeeping.Block, cert *agreement.Certificate, downloadDuration time.Duration, psp *peerSelectorPeer, stop bool, err error) { psp, err = cs.blocksDownloadPeerSelector.getNextPeer() if err != nil { - if err == errPeerSelectorNoPeerPoolsAvailable { + if errors.Is(err, errPeerSelectorNoPeerPoolsAvailable) { cs.log.Infof("fetchBlock: unable to obtain a list of peers to retrieve the latest block from; will retry shortly.") // this is a possible on startup, since the network package might have yet to retrieve the list of peers. time.Sleep(noPeersAvailableSleepInterval) @@ -718,7 +719,7 @@ func (cs *CatchpointCatchupService) processStageSwitch() (err error) { // stopOrAbort is called when any of the stage processing function sees that cs.ctx has been canceled. It can be // due to the end user attempting to abort the current catchpoint catchup operation or due to a node shutdown. func (cs *CatchpointCatchupService) stopOrAbort() error { - if cs.abortCtx.Err() == context.Canceled { + if errors.Is(cs.abortCtx.Err(), context.Canceled) { return cs.abort(context.Canceled) } return nil @@ -749,7 +750,7 @@ func (cs *CatchpointCatchupService) updateStage(newStage ledger.CatchpointCatchu return nil } -// updateNodeCatchupMode requests the node to change it's operational mode from +// updateNodeCatchupMode requests the node to change its operational mode from // catchup mode to normal mode and vice versa. func (cs *CatchpointCatchupService) updateNodeCatchupMode(catchupModeEnabled bool) { newCtxCh := cs.node.SetCatchpointCatchupMode(catchupModeEnabled) @@ -802,24 +803,7 @@ func (cs *CatchpointCatchupService) updateBlockRetrievalStatistics(acquiredBlock } func (cs *CatchpointCatchupService) initDownloadPeerSelector() { - cs.blocksDownloadPeerSelector = cs.makeCatchpointPeerSelector() -} - -func (cs *CatchpointCatchupService) makeCatchpointPeerSelector() *peerSelector { - if cs.config.EnableCatchupFromArchiveServers { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - }) - } else { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, - }) - } + cs.blocksDownloadPeerSelector = makeCatchpointPeerSelector(cs.net) } // checkLedgerDownload sends a HEAD request to the ledger endpoint of peers to validate the catchpoint's availability @@ -830,10 +814,9 @@ func (cs *CatchpointCatchupService) checkLedgerDownload() error { if err != nil { return fmt.Errorf("failed to parse catchpoint label : %v", err) } - peerSelector := cs.makeCatchpointPeerSelector() ledgerFetcher := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) for i := 0; i < cs.config.CatchupLedgerDownloadRetryAttempts; i++ { - psp, peerError := peerSelector.getNextPeer() + psp, peerError := cs.blocksDownloadPeerSelector.getNextPeer() if peerError != nil { return err } diff --git a/catchup/classBasedPeerSelector.go b/catchup/classBasedPeerSelector.go new file mode 100644 index 0000000000..9ab9e6d71d --- /dev/null +++ b/catchup/classBasedPeerSelector.go @@ -0,0 +1,156 @@ +// Copyright (C) 2019-2024 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 catchup + +import ( + "errors" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-deadlock" + "time" +) + +// classBasedPeerSelector is a rankPooledPeerSelector that tracks and ranks classes of peers based on their response behavior. +// It is used to select the most appropriate peers to download blocks from - this is most useful when catching up +// and needing to figure out whether the blocks can be retrieved from relay nodes or require archive nodes. +// The ordering of the peerSelectors directly determines the priority of the classes of peers. +type classBasedPeerSelector struct { + mu deadlock.Mutex + peerSelectors []*wrappedPeerSelector +} + +func makeClassBasedPeerSelector(peerSelectors []*wrappedPeerSelector) *classBasedPeerSelector { + return &classBasedPeerSelector{ + peerSelectors: peerSelectors, + } +} + +func (c *classBasedPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { + c.mu.Lock() + defer c.mu.Unlock() + + oldRank, newRank := -1, -1 + for _, wp := range c.peerSelectors { + // See if the peer is in the class, ranking it appropriately if so + if psp.peerClass != wp.peerClass { + continue + } + + oldRank, newRank = wp.peerSelector.rankPeer(psp, rank) + if oldRank < 0 || newRank < 0 { + // Peer not found in this selector + continue + } + + // Peer was in this class, if there was any kind of download issue, we increment the failure count + if rank >= peerRankNoBlockForRound { + wp.downloadFailures++ + } + + break + } + + return oldRank, newRank +} + +func (c *classBasedPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + c.mu.Lock() + defer c.mu.Unlock() + + for _, wp := range c.peerSelectors { + rank = wp.peerSelector.peerDownloadDurationToRank(psp, blockDownloadDuration) + // If rank is peerRankInvalidDownload, we check the next class's rankPooledPeerSelector + if rank >= peerRankInvalidDownload { + continue + } + // Should be a legit ranking, we return it + return rank + } + // If we reached here, we have exhausted all classes without finding the peer + return peerRankInvalidDownload +} + +func (c *classBasedPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { + c.mu.Lock() + defer c.mu.Unlock() + return c.internalGetNextPeer(0) +} + +// internalGetNextPeer is a helper function that should be called with the lock held +func (c *classBasedPeerSelector) internalGetNextPeer(recurseCount int8) (psp *peerSelectorPeer, err error) { + // Safety check to prevent infinite recursion + if recurseCount > 1 { + return nil, errPeerSelectorNoPeerPoolsAvailable + } + selectorDisabledCount := 0 + for _, wp := range c.peerSelectors { + if wp.downloadFailures > wp.toleranceFactor { + // peerSelector is disabled for now, we move to the next one + selectorDisabledCount++ + continue + } + psp, err = wp.peerSelector.getNextPeer() + + if err != nil { + // This is mostly just future-proofing, as we don't expect any other errors from getNextPeer + if errors.Is(err, errPeerSelectorNoPeerPoolsAvailable) { + // We penalize this class the equivalent of one download failure (in case this is transient) + wp.downloadFailures++ + } + continue + } + return psp, nil + } + // If we reached here, we have exhausted all classes and still have no peers + // IFF all classes are disabled, we reset the downloadFailures for all classes and start over + if len(c.peerSelectors) != 0 && selectorDisabledCount == len(c.peerSelectors) { + for _, wp := range c.peerSelectors { + wp.downloadFailures = 0 + } + // Recurse to try again, we should have at least one class enabled now + return c.internalGetNextPeer(recurseCount + 1) + } + // If we reached here, we have exhausted all classes without finding a peer, not due to all classes being disabled + return nil, errPeerSelectorNoPeerPoolsAvailable +} + +type wrappedPeerSelector struct { + peerSelector peerSelector // The underlying peerSelector for this class + peerClass network.PeerOption // The class of peers the peerSelector is responsible for + toleranceFactor int // The number of times we can net fail for any reason before we move to the next class's rankPooledPeerSelector + downloadFailures int // The number of times we have failed to download a block from this class's rankPooledPeerSelector since it was last reset +} + +// makeCatchpointPeerSelector returns a classBasedPeerSelector that selects peers based on their class and response behavior. +// These are the preferred configurations for the catchpoint service. +func makeCatchpointPeerSelector(net peersRetriever) peerSelector { + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersPhonebookRelays, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}), + toleranceFactor: 10, + }, + } + + return makeClassBasedPeerSelector(wrappedPeerSelectors) +} diff --git a/catchup/classBasedPeerSelector_test.go b/catchup/classBasedPeerSelector_test.go new file mode 100644 index 0000000000..0110663f87 --- /dev/null +++ b/catchup/classBasedPeerSelector_test.go @@ -0,0 +1,498 @@ +// Copyright (C) 2019-2024 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 catchup + +import ( + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +// Use to mock the wrapped peer selectors where warranted +type mockPeerSelector struct { + mockRankPeer func(psp *peerSelectorPeer, rank int) (int, int) + mockPeerDownloadDurationToRank func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) + mockGetNextPeer func() (psp *peerSelectorPeer, err error) +} + +func (m mockPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { + return m.mockRankPeer(psp, rank) +} + +func (m mockPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return m.mockPeerDownloadDurationToRank(psp, blockDownloadDuration) +} + +func (m mockPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { + return m.mockGetNextPeer() +} + +func TestClassBasedPeerSelector_makeClassBasedPeerSelector(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{}, + toleranceFactor: 3, + }, + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{}, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{}, + toleranceFactor: 10, + }, + } + + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // The selectors should be sorted by priority + require.Equal(t, 3, len(cps.peerSelectors)) + require.Equal(t, network.PeersPhonebookRelays, cps.peerSelectors[0].peerClass) + require.Equal(t, network.PeersConnectedOut, cps.peerSelectors[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, cps.peerSelectors[2].peerClass) +} + +func TestClassBasedPeerSelector_rankPeer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + + // Create a class based peer selector initially with the first wrapped peer selector not having the peer, + // second one having it, and a third one not having it + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + } + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // Peer is found in second selector, rank is within range for a block found + oldRank, newRank := cps.rankPeer(mockPeer, 50) + + require.Equal(t, 10, oldRank) + require.Equal(t, 50, newRank) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + + // Peer is found in second selector, rank is >= peerRankNoBlockForRound + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 1, cps.peerSelectors[1].downloadFailures) + + // We fail to find a block for round 3 more times, download failures should reflect that. + cps.rankPeer(mockPeer, peerRankNoBlockForRound) + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 3, cps.peerSelectors[1].downloadFailures) + + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 4, cps.peerSelectors[1].downloadFailures) + + // Now, feed peers that are not in any of the selectors - it should return -1, -1 + mockPeer2 := &peerSelectorPeer{ + peerClass: network.PeersConnectedIn, + } + + oldRank, newRank = cps.rankPeer(mockPeer2, 50) + require.Equal(t, -1, oldRank) + require.Equal(t, -1, newRank) + + // While this will match class, the selectors will not have it + mockPeer3 := &peerSelectorPeer{ + peerClass: network.PeersConnectedOut, + } + + oldRank, newRank = cps.rankPeer(mockPeer3, 50) + require.Equal(t, -1, oldRank) + require.Equal(t, -1, newRank) + + // Last sanity check, we should have zero download failures for the first and third selectors + require.Equal(t, 0, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) +} + +func TestClassBasedPeerSelector_peerDownloadDurationToRank(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{} + testDuration := 50 * time.Millisecond + + // Create a class based peer selector initially with the first wrapped peer selector not having the peer, + // second one having it, and a third one not having it + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + if psp == mockPeer && blockDownloadDuration == testDuration { + return peerRank0HighBlockTime + } + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + } + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // The peer is found in the second selector, so the rank should be peerRank0HighBlockTime + rank := cps.peerDownloadDurationToRank(mockPeer, testDuration) + require.Equal(t, peerRank0HighBlockTime, rank) + + // The peer is not found in any of the selectors, so the rank should be peerRankInvalidDownload + mockPeer2 := &peerSelectorPeer{} + + rank = cps.peerDownloadDurationToRank(mockPeer2, testDuration) + require.Equal(t, peerRankInvalidDownload, rank) +} + +func TestClassBasedPeerSelector_getNextPeer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + + // Create a class based peer selector initially with the first wrapped peer selector not having any peers, + // second one having a peer, and a third one not having any peers + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer, nil + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + }, + toleranceFactor: 3, + }, + } + + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + peerResult, err := cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + + // Update selector to not return any peers + wrappedPeerSelectors[1].peerSelector = mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, peerResult) + require.Equal(t, errPeerSelectorNoPeerPoolsAvailable, err) + + // Create a class based peer selector initially with all wrapped peer selectors having peers. + // The peers should always come from the first one repeatedly since rankings are not changed. + mockPeer = &peerSelectorPeer{ + peerClass: network.PeersConnectedOut, + } + mockPeer2 := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + mockPeer3 := &peerSelectorPeer{ + peerClass: network.PeersPhonebookArchivalNodes, + } + + wrappedPeerSelectors = []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer2, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer2 { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 10, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer3, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer3 { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + } + + cps = makeClassBasedPeerSelector(wrappedPeerSelectors) + + // We should always get the peer from the top priority selector since rankings are not updated/list is not re-sorted. + for i := 0; i < 10; i++ { + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + } + + // Okay, record enough download failures to disable the first selector + for i := 0; i < 4; i++ { + cps.rankPeer(mockPeer, peerRankNoBlockForRound) + } + + // Now, we should get the peer from the second selector + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer2, peerResult) + + // Sanity check the download failures for each selector + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) + + // Now, record download failures just up to the tolerance factor for the second selector + for i := 0; i < 10; i++ { + cps.rankPeer(mockPeer2, peerRankNoBlockForRound) + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer2, peerResult) + + // One more should push us to the third selector + cps.rankPeer(mockPeer2, peerRankNoBlockForRound) + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer3, peerResult) + + // Check of the download failures for each selector + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 11, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) + + // Now, record download failures just up to the tolerance factor for the third selector + for i := 0; i < 3; i++ { + cps.rankPeer(mockPeer3, peerRankNoBlockForRound) + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer3, peerResult) + + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 11, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 3, cps.peerSelectors[2].downloadFailures) + + // One more failure should reset ALL download failures (and grab a peer from the first selector) + cps.rankPeer(mockPeer3, peerRankNoBlockForRound) + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + + // Check of the download failures for each selector, should have been reset + require.Equal(t, 0, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) +} + +func TestClassBasedPeerSelector_integration(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockP1Peer := mockHTTPPeer{address: "p1"} + mockP2Peer := mockHTTPPeer{address: "p2"} + + mockP1WrappedPeer := &peerSelectorPeer{&mockP1Peer, network.PeersPhonebookRelays} + mockP2WrappedPeer := &peerSelectorPeer{&mockP2Peer, network.PeersPhonebookArchivalNodes} + + net := makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { + if len(options) > 0 { + switch options[0] { + case network.PeersPhonebookRelays: + return []network.Peer{&mockP1Peer} + case network.PeersPhonebookArchivalNodes: + return []network.Peer{&mockP2Peer} + default: + return []network.Peer{&mockP1Peer, &mockP2Peer} + } + } + return nil + }) + // Create a class based peer selector with a few wrapped peer selectors + cps := makeCatchpointPeerSelector(net).(*classBasedPeerSelector) + + // We should get the peer from the first priority selector, PeersPhonebookRelays + peerResult, err := cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + // Normal expected usage: rank the peer + durationRank := cps.peerDownloadDurationToRank(mockP1WrappedPeer, 500) + oldRank, newRank := cps.rankPeer(mockP1WrappedPeer, durationRank) + + require.Equal(t, 0, oldRank) + require.Equal(t, durationRank, newRank) + + // Let's simulate a few download failures (not enough to disable the selector) + for i := 0; i < 3; i++ { + expectedOldRank := newRank + peerResult, err = cps.getNextPeer() + + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + oldRank, newRank = cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound) + + require.Equal(t, expectedOldRank, oldRank) + // Should be increasing with no block penalties + require.True(t, newRank >= oldRank) + } + + // Sanity check, still should be the same peer (from phonebook selector) + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + // Rank the peer to follow normal usage + durationRank = cps.peerDownloadDurationToRank(mockP1WrappedPeer, 500) + expectedOldRank := newRank + oldRank, newRank = cps.rankPeer(mockP1WrappedPeer, durationRank) + + require.Equal(t, expectedOldRank, oldRank) + // Rank should not go up after successful download + require.True(t, newRank <= oldRank) + + // Now, let's simulate enough download failures to disable the first selector + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound) + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP2WrappedPeer, peerResult) + + // Normal expected usage: rank the peer + durationRank = cps.peerDownloadDurationToRank(mockP2WrappedPeer, 500) + oldRank, newRank = cps.rankPeer(mockP2WrappedPeer, durationRank) + + require.Equal(t, 0, oldRank) + require.Equal(t, durationRank, newRank) + + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) +} diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 4ceda8d42d..05556bb24b 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -29,12 +29,12 @@ import ( ) const ( - // peerRankInitialFirstPriority is the high-priority peers group ( typically, archivers ) + // peerRankInitialFirstPriority is the high-priority peers group peerRankInitialFirstPriority = 0 peerRank0LowBlockTime = 1 peerRank0HighBlockTime = 199 - // peerRankInitialSecondPriority is the second priority peers group ( typically, relays ) + // peerRankInitialSecondPriority is the second priority peers group peerRankInitialSecondPriority = 200 peerRank1LowBlockTime = 201 peerRank1HighBlockTime = 399 @@ -88,7 +88,7 @@ type peerClass struct { peerClass network.PeerOption } -// the peersRetriever is a subset of the network.GossipNode used to ensure that we can create an instance of the peerSelector +// the peersRetriever is a subset of the network.GossipNode used to ensure that we can create an instance of the rankPooledPeerSelector // for testing purposes, providing just the above function. type peersRetriever interface { // Get a list of Peers we could potentially send a direct message to. @@ -109,14 +109,20 @@ type peerPool struct { peers []peerPoolEntry } -// peerSelector is a helper struct used to select the next peer to try and connect to +type peerSelector interface { + rankPeer(psp *peerSelectorPeer, rank int) (int, int) + peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) + getNextPeer() (psp *peerSelectorPeer, err error) +} + +// rankPooledPeerSelector is a helper struct used to select the next peer to try and connect to // for various catchup purposes. Unlike the underlying network GetPeers(), it allows the // client to provide feedback regarding the peer's performance, and to have the subsequent // query(s) take advantage of that intel. -type peerSelector struct { +type rankPooledPeerSelector struct { mu deadlock.Mutex net peersRetriever - // peerClasses is the list of peer classes we want to have in the peerSelector. + // peerClasses is the list of peer classes we want to have in the rankPooledPeerSelector. peerClasses []peerClass // pools is the list of peer pools, each pool contains a list of peers with the same rank. pools []peerPool @@ -284,9 +290,9 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera return bounded } -// makePeerSelector creates a peerSelector, given a peersRetriever and peerClass array. -func makePeerSelector(net peersRetriever, initialPeersClasses []peerClass) *peerSelector { - selector := &peerSelector{ +// makeRankPooledPeerSelector creates a rankPooledPeerSelector, given a peersRetriever and peerClass array. +func makeRankPooledPeerSelector(net peersRetriever, initialPeersClasses []peerClass) *rankPooledPeerSelector { + selector := &rankPooledPeerSelector{ net: net, peerClasses: initialPeersClasses, } @@ -296,7 +302,7 @@ func makePeerSelector(net peersRetriever, initialPeersClasses []peerClass) *peer // getNextPeer returns the next peer. It randomally selects a peer from a pool that has // the lowest rank value. Given that the peers are grouped by their ranks, allow us to // prioritize peers based on their class and/or performance. -func (ps *peerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { +func (ps *rankPooledPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { ps.mu.Lock() defer ps.mu.Unlock() ps.refreshAvailablePeers() @@ -317,7 +323,7 @@ func (ps *peerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { // rankPeer ranks a given peer. // return the old value and the new updated value. // updated value could be different from the input rank. -func (ps *peerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { +func (ps *rankPooledPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { if psp == nil { return -1, -1 } @@ -384,7 +390,7 @@ func (ps *peerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { } // peerDownloadDurationToRank calculates the rank for a peer given a peer and the block download time. -func (ps *peerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { +func (ps *rankPooledPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { ps.mu.Lock() defer ps.mu.Unlock() poolIdx, peerIdx := ps.findPeer(psp) @@ -409,7 +415,7 @@ func (ps *peerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockD // addToPool adds a given peer to the correct group. If no group exists for that peer's rank, // a new group is created. // The method return true if a new group was created ( suggesting that the pools list would need to be re-ordered ), or false otherwise. -func (ps *peerSelector) addToPool(peer network.Peer, rank int, class peerClass, peerHistory *historicStats) bool { +func (ps *rankPooledPeerSelector) addToPool(peer network.Peer, rank int, class peerClass, peerHistory *historicStats) bool { // see if we already have a list with that rank: for i, pool := range ps.pools { if pool.rank == rank { @@ -423,7 +429,7 @@ func (ps *peerSelector) addToPool(peer network.Peer, rank int, class peerClass, } // sort the pools array in an ascending order according to the rank of each pool. -func (ps *peerSelector) sort() { +func (ps *rankPooledPeerSelector) sort() { sort.SliceStable(ps.pools, func(i, j int) bool { return ps.pools[i].rank < ps.pools[j].rank }) @@ -443,7 +449,7 @@ func peerAddress(peer network.Peer) string { // refreshAvailablePeers reload the available peers from the network package, add new peers along with their // corresponding initial rank, and deletes peers that have been dropped by the network package. -func (ps *peerSelector) refreshAvailablePeers() { +func (ps *rankPooledPeerSelector) refreshAvailablePeers() { existingPeers := make(map[network.PeerOption]map[string]bool) for _, pool := range ps.pools { for _, localPeer := range pool.peers { @@ -501,7 +507,7 @@ func (ps *peerSelector) refreshAvailablePeers() { // findPeer look into the peer pool and find the given peer. // The method returns the pool and peer indices if a peer was found, or (-1, -1) otherwise. -func (ps *peerSelector) findPeer(psp *peerSelectorPeer) (poolIdx, peerIdx int) { +func (ps *rankPooledPeerSelector) findPeer(psp *peerSelectorPeer) (poolIdx, peerIdx int) { peerAddr := peerAddress(psp.Peer) if peerAddr == "" { return -1, -1 diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index 2a907934a8..7aa373d280 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -131,10 +131,10 @@ func TestPeerSelector_RankPeer(t *testing.T) { peers := []network.Peer{&mockHTTPPeer{address: "12345"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return peers - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, ) psp, err := peerSelector.getNextPeer() @@ -191,17 +191,17 @@ func TestPeerSelector_PeerDownloadRanking(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "1234"}, &mockHTTPPeer{address: "5678"}} peers2 := []network.Peer{&mockHTTPPeer{address: "abcd"}, &mockHTTPPeer{address: "efgh"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) archivalPeer, err := peerSelector.getNextPeer() @@ -240,10 +240,10 @@ func TestPeerSelector_FindMissingPeer(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return []network.Peer{} - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, ) poolIdx, peerIdx := peerSelector.findPeer(&peerSelectorPeer{mockHTTPPeer{address: "abcd"}, 0}) @@ -258,17 +258,17 @@ func TestPeerSelector_HistoricData(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -332,17 +332,17 @@ func TestPeerSelector_PeersDownloadFailed(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -408,17 +408,17 @@ func TestPeerSelector_Penalty(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -468,44 +468,39 @@ func TestPeerSelector_PeerDownloadDurationToRank(t *testing.T) { peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} peers3 := []network.Peer{&mockHTTPPeer{address: "c1"}, &mockHTTPPeer{address: "c2"}} peers4 := []network.Peer{&mockHTTPPeer{address: "d1"}, &mockHTTPPeer{address: "b2"}} - peers5 := []network.Peer{&mockHTTPPeer{address: "e1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookRelays { peers = append(peers, peers1...) - } else if opt == network.PeersPhonebookRelays { - peers = append(peers, peers2...) } else if opt == network.PeersConnectedOut { - peers = append(peers, peers3...) + peers = append(peers, peers2...) } else if opt == network.PeersPhonebookArchivalNodes { - peers = append(peers, peers4...) + peers = append(peers, peers3...) } else { // PeersConnectedIn - peers = append(peers, peers5...) + peers = append(peers, peers4...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}}, + }), []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}}, ) _, err := peerSelector.getNextPeer() require.NoError(t, err) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank0LowBlockTime, peerRank0HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers1[0], network.PeersPhonebookArchivers}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers1[0], network.PeersPhonebookRelays}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank1LowBlockTime, peerRank1HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers2[0], network.PeersPhonebookRelays}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers2[0], network.PeersConnectedOut}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank2LowBlockTime, peerRank2HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers3[0], network.PeersConnectedOut}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers3[0], network.PeersPhonebookArchivalNodes}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank3LowBlockTime, peerRank3HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers4[0], network.PeersPhonebookArchivalNodes}, 500*time.Millisecond)) - require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank4LowBlockTime, peerRank4HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers5[0], network.PeersConnectedIn}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers4[0], network.PeersConnectedIn}, 500*time.Millisecond)) } @@ -513,7 +508,7 @@ func TestPeerSelector_LowerUpperBounds(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, @@ -536,7 +531,7 @@ func TestPeerSelector_FullResetRequestPenalty(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivers} + class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivalNodes} hs := makeHistoricStatus(10, class) hs.push(5, 1, class) require.Equal(t, 1, len(hs.requestGaps)) @@ -551,7 +546,7 @@ func TestPeerSelector_PenaltyBounds(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers} + class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes} hs := makeHistoricStatus(peerHistoryWindowSize, class) for x := 0; x < 65; x++ { r0 := hs.push(peerRank2LowBlockTime+50, uint64(x+1), class) @@ -578,11 +573,11 @@ func TestPeerSelector_ClassUpperBound(t *testing.T) { t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} - pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} - peerSelector := makePeerSelector( + pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } } @@ -613,11 +608,11 @@ func TestPeerSelector_ClassLowerBound(t *testing.T) { t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} - pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} - peerSelector := makePeerSelector( + pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } } @@ -644,17 +639,17 @@ func TestPeerSelector_EvictionAndUpgrade(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}} peers2 := []network.Peer{&mockHTTPPeer{address: "a1"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -682,7 +677,7 @@ func TestPeerSelector_RefreshAvailablePeers(t *testing.T) { // check new peers added to the pool p1 := mockHTTPPeer{address: "p1"} p2 := mockHTTPPeer{address: "p2"} - ps := peerSelector{ + ps := rankPooledPeerSelector{ peerClasses: []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, diff --git a/catchup/service.go b/catchup/service.go index 1093f851a2..5c6609b236 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -38,10 +38,7 @@ import ( "github.com/algorand/go-algorand/util/execpool" ) -const catchupPeersForSync = 10 -const blockQueryPeerLimit = 10 - -// uncapParallelDownloadRate is a simple threshold to detect whether or not the node is caught up. +// uncapParallelDownloadRate is a simple threshold to detect whether the node is caught up. // If a block is downloaded in less than this duration, it's assumed that the node is not caught up // and allow the block downloader to start N=parallelBlocks concurrent fetches. const uncapParallelDownloadRate = time.Second @@ -76,7 +73,7 @@ type Ledger interface { WaitMem(r basics.Round) chan struct{} } -// Service represents the catchup service. Once started and until it is stopped, it ensures that the ledger is up to date with network. +// Service represents the catchup service. Once started and until it is stopped, it ensures that the ledger is up-to-date with network. type Service struct { // disableSyncRound, provided externally, is the first round we will _not_ fetch from the network // any round >= disableSyncRound will not be fetched. If set to 0, it will be disregarded. @@ -266,7 +263,7 @@ const errNoBlockForRoundThreshold = 5 // - If we couldn't fetch the block (e.g. if there are no peers available, or we've reached the catchupRetryLimit) // - If the block is already in the ledger (e.g. if agreement service has already written it) // - If the retrieval of the previous block was unsuccessful -func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCompleteChan chan struct{}, lookbackComplete chan struct{}, peerSelector *peerSelector) bool { +func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCompleteChan chan struct{}, lookbackComplete chan struct{}, peerSelector peerSelector) bool { // If sync-ing this round is not intended, don't fetch it if dontSyncRound := s.GetDisableSyncRound(); dontSyncRound != 0 && r >= basics.Round(dontSyncRound) { return false @@ -318,7 +315,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo block, cert, blockDownloadDuration, err := s.innerFetch(ctx, r, peer) if err != nil { - if err == errLedgerAlreadyHasBlock { + if errors.Is(err, errLedgerAlreadyHasBlock) { // ledger already has the block, no need to request this block. // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): the block is already in the ledger. The catchup is complete", r) @@ -329,7 +326,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo if errors.As(err, &nbfe) { failureRank = peerRankNoBlockForRound // remote peer doesn't have the block, try another peer - // quit if the the same peer peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times + // quit if the same peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times if s.followLatest { // back off between retries to allow time for the next block to appear; // this will provide 50s (catchupRetryLimit * followLatestBackoff) of @@ -427,7 +424,8 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo // if the context expired, just exit. return false } - if errNSBE, ok := err.(ledgercore.ErrNonSequentialBlockEval); ok && errNSBE.EvaluatorRound <= errNSBE.LatestRound { + var errNSBE ledgercore.ErrNonSequentialBlockEval + if errors.As(err, &errNSBE) && errNSBE.EvaluatorRound <= errNSBE.LatestRound { // the block was added to the ledger from elsewhere after fetching it here // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): after fetching the block, it is already in the ledger. The catchup is complete", r) @@ -442,16 +440,19 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo } if err != nil { - switch err.(type) { - case ledgercore.ErrNonSequentialBlockEval: + var errNonSequentialBlockEval ledgercore.ErrNonSequentialBlockEval + var blockInLedgerError ledgercore.BlockInLedgerError + var protocolErr protocol.Error + switch { + case errors.As(err, &errNonSequentialBlockEval): s.log.Infof("fetchAndWrite(%d): no need to re-evaluate historical block", r) return true - case ledgercore.BlockInLedgerError: + case errors.As(err, &blockInLedgerError): // the block was added to the ledger from elsewhere after fetching it here // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): after fetching the block, it is already in the ledger. The catchup is complete", r) return false - case protocol.Error: + case errors.As(err, &protocolErr): if !s.protocolErrorLogged { logging.Base().Errorf("fetchAndWrite(%v): unrecoverable protocol error detected: %v", r, err) s.protocolErrorLogged = true @@ -491,8 +492,8 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { } }() - peerSelector := createPeerSelector(s.net, s.cfg, true) - if _, err := peerSelector.getNextPeer(); err == errPeerSelectorNoPeerPoolsAvailable { + ps := createPeerSelector(s.net) + if _, err := ps.getNextPeer(); err != nil { s.log.Debugf("pipelinedFetch: was unable to obtain a peer to retrieve the block from") return } @@ -527,7 +528,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { go func(r basics.Round) { prev := s.ledger.WaitMem(r - 1) seed := s.ledger.WaitMem(r.SubSaturate(basics.Round(seedLookback))) - done <- s.fetchAndWrite(ctx, r, prev, seed, peerSelector) + done <- s.fetchAndWrite(ctx, r, prev, seed, ps) wg.Done() }(nextRound) @@ -751,9 +752,9 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy peerErrors := map[network.Peer]int{} blockHash := bookkeeping.BlockHash(cert.Proposal.BlockDigest) // semantic digest (i.e., hash of the block header), not byte-for-byte digest - peerSelector := createPeerSelector(s.net, s.cfg, false) + ps := createPeerSelector(s.net) for s.ledger.LastRound() < cert.Round { - psp, getPeerErr := peerSelector.getNextPeer() + psp, getPeerErr := ps.getNextPeer() if getPeerErr != nil { s.log.Debugf("fetchRound: was unable to obtain a peer to retrieve the block from") s.net.RequestConnectOutgoing(true, s.ctx.Done()) @@ -783,19 +784,19 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy time.Sleep(50 * time.Millisecond) } if count > errNoBlockForRoundThreshold*10 { - // for the low number of connected peers (like 2) the following scenatio is possible: + // for the low number of connected peers (like 2) the following scenario is possible: // - both peers do not have the block // - peer selector punishes one of the peers more than the other - // - the punoshed peer gets the block, and the less punished peer stucks. + // - the punished peer gets the block, and the less punished peer stucks. // It this case reset the peer selector to let it re-learn priorities. - peerSelector = createPeerSelector(s.net, s.cfg, false) + ps = createPeerSelector(s.net) } } peerErrors[peer]++ } // remote peer doesn't have the block, try another peer logging.Base().Warnf("fetchRound could not acquire block, fetcher errored out: %v", err) - peerSelector.rankPeer(psp, failureRank) + ps.rankPeer(psp, failureRank) continue } @@ -805,7 +806,7 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy } // Otherwise, fetcher gave us the wrong block logging.Base().Warnf("fetcher gave us bad/wrong block (for round %d): fetched hash %v; want hash %v", cert.Round, block.Hash(), blockHash) - peerSelector.rankPeer(psp, peerRankInvalidDownload) + ps.rankPeer(psp, peerRankInvalidDownload) // As a failsafe, if the cert we fetched is valid but for the wrong block, panic as loudly as possible if cert.Round == fetchedCert.Round && @@ -866,76 +867,33 @@ func (s *Service) roundIsNotSupported(nextRound basics.Round) bool { return true } -func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch bool) *peerSelector { - var peerClasses []peerClass - if cfg.EnableCatchupFromArchiveServers { - if pipelineFetch { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } - } - } else { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersPhonebookArchivers}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivers}, - } - } - } - } else { - if pipelineFetch { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } - } - } else { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } - } - } +func createPeerSelector(net network.GossipNode) peerSelector { + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}), + toleranceFactor: 10, + }, + { + peerClass: network.PeersConnectedIn, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedIn}}), + toleranceFactor: 3, + }, } - return makePeerSelector(net, peerClasses) + + return makeClassBasedPeerSelector(wrappedPeerSelectors) } diff --git a/catchup/service_test.go b/catchup/service_test.go index c5b8cfccab..045a0438f2 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -954,146 +954,27 @@ func TestCatchupUnmatchedCertificate(t *testing.T) { } } -// TestCreatePeerSelector tests if the correct peer selector coonfigurations are prepared +// TestCreatePeerSelector tests if the correct peer selector configurations are prepared func TestCreatePeerSelector(t *testing.T) { partitiontest.PartitionTest(t) - // Make Service - cfg := defaultConfig + s := MakeService(logging.Base(), defaultConfig, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) + ps := createPeerSelector(s.net) + + cps, ok := ps.(*classBasedPeerSelector) + require.True(t, ok) + + require.Equal(t, 4, len(cps.peerSelectors)) + + require.Equal(t, network.PeersConnectedOut, cps.peerSelectors[0].peerClass) + require.Equal(t, network.PeersPhonebookRelays, cps.peerSelectors[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, cps.peerSelectors[2].peerClass) + require.Equal(t, network.PeersConnectedIn, cps.peerSelectors[3].peerClass) - cfg.EnableCatchupFromArchiveServers = true - - cfg.NetAddress = "someAddress" - s := MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps := createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 5, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - require.Equal(t, peerRankInitialFifthPriority, ps.peerClasses[4].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[4].peerClass) - - // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress == ""; pipelineFetch = true; - cfg.EnableCatchupFromArchiveServers = true - cfg.NetAddress = "" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 4, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - - // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress != ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = true - cfg.NetAddress = "someAddress" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 5, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - require.Equal(t, peerRankInitialFifthPriority, ps.peerClasses[4].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[4].peerClass) - - // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress == ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = true - cfg.NetAddress = "" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 4, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[3].peerClass) - - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = true - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "someAddress" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, true) - - require.Equal(t, 4, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) - - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = true - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, true) - - require.Equal(t, 3, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "someAddress" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 4, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = false - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "" - s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) - ps = createPeerSelector(s.net, s.cfg, false) - - require.Equal(t, 3, len(ps.peerClasses)) - require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) - require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) - require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + require.Equal(t, 3, cps.peerSelectors[0].toleranceFactor) + require.Equal(t, 3, cps.peerSelectors[1].toleranceFactor) + require.Equal(t, 10, cps.peerSelectors[2].toleranceFactor) + require.Equal(t, 3, cps.peerSelectors[3].toleranceFactor) } func TestServiceStartStop(t *testing.T) { diff --git a/cmd/algokey/common.go b/cmd/algokey/common.go index 8fc5867db9..0259dc5e05 100644 --- a/cmd/algokey/common.go +++ b/cmd/algokey/common.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/passphrase" + "github.com/muesli/termenv" ) const ( @@ -122,3 +123,18 @@ func readFile(filename string) ([]byte, error) { } return os.ReadFile(filename) } + +// printDiscreetly Print a secret string to an alternate screen, +// so the string isn't printed to the terminal. +func printDiscreetly(w io.Writer, promptMsg, secretMsg string) error { + output := termenv.NewOutput(w) + output.AltScreen() + defer output.ExitAltScreen() + if _, err := fmt.Fprintf(output, "%s\n\n%s\n\nPress 'Enter' key to continue.", promptMsg, secretMsg); err != nil { + return err + } + if _, err := fmt.Scanln(); err != nil { + return err + } + return nil +} diff --git a/cmd/algokey/common_test.go b/cmd/algokey/common_test.go new file mode 100644 index 0000000000..2256342616 --- /dev/null +++ b/cmd/algokey/common_test.go @@ -0,0 +1,39 @@ +// Copyright (C) 2019-2024 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 main + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" +) + +func Test_printDiscreetly(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + buf := new(bytes.Buffer) + var ( + promptMsg = "Prompt Message" + secretMsg = "Secret Message" + ) + require.NoError(t, printDiscreetly(buf, promptMsg, secretMsg)) + require.Contains(t, buf.String(), promptMsg) + require.Contains(t, buf.String(), secretMsg) +} diff --git a/cmd/algokey/generate.go b/cmd/algokey/generate.go index 6382fa3a0c..6b1c409e68 100644 --- a/cmd/algokey/generate.go +++ b/cmd/algokey/generate.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "os" "github.com/spf13/cobra" @@ -27,10 +28,12 @@ import ( var generateKeyfile string var generatePubkeyfile string +var generateDiscreet bool func init() { generateCmd.Flags().StringVarP(&generateKeyfile, "keyfile", "f", "", "Private key filename") generateCmd.Flags().StringVarP(&generatePubkeyfile, "pubkeyfile", "p", "", "Public key filename") + generateCmd.Flags().BoolVar(&generateDiscreet, "discreet", false, "Print mnemonic discreetly to an alternate screen") } var generateCmd = &cobra.Command{ @@ -46,7 +49,14 @@ var generateCmd = &cobra.Command{ key := crypto.GenerateSignatureSecrets(seed) publicKeyChecksummed := basics.Address(key.SignatureVerifier).String() - fmt.Printf("Private key mnemonic: %s\n", mnemonic) + if generateDiscreet { + if err := printDiscreetly(os.Stderr, "**Important** write this private key mnemonic phrase in a safe place. Do not share it to anyone", fmt.Sprintf("Private key mnemonic: %s", mnemonic)); err != nil { + fmt.Fprintf(os.Stderr, "Fail to print mnemonic: %v", err) + os.Exit(1) + } + } else { + fmt.Printf("Private key mnemonic: %s\n", mnemonic) + } fmt.Printf("Public key: %s\n", publicKeyChecksummed) if generateKeyfile != "" { diff --git a/cmd/catchpointdump/net.go b/cmd/catchpointdump/net.go index 2de40c3c2d..41e1fd1dd0 100644 --- a/cmd/catchpointdump/net.go +++ b/cmd/catchpointdump/net.go @@ -78,7 +78,7 @@ var netCmd = &cobra.Command{ if relayAddress != "" { addrs = []string{relayAddress} } else { - addrs, err = tools.ReadFromSRV("algobootstrap", "tcp", networkName, "", false) + addrs, err = tools.ReadFromSRV(context.Background(), "algobootstrap", "tcp", networkName, "", false) if err != nil || len(addrs) == 0 { reportErrorf("Unable to bootstrap records for '%s' : %v", networkName, err) } diff --git a/cmd/netgoal/generate.go b/cmd/netgoal/generate.go index 882cd0f9d0..9e87004a05 100644 --- a/cmd/netgoal/generate.go +++ b/cmd/netgoal/generate.go @@ -475,6 +475,9 @@ func saveTemplateToDisk(template remote.DeployedNetworkConfig, filename string) } func saveGoalTemplateToDisk(template netdeploy.NetworkTemplate, filename string) error { + if lastPartKeyRound != 0 { + template.Genesis.LastPartKeyRound = lastPartKeyRound + } f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err == nil { defer f.Close() diff --git a/config/localTemplate.go b/config/localTemplate.go index c6e1ba0a2c..ce4294de01 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -44,13 +44,13 @@ type Local struct { // for an existing parameter. This field tag must be updated any time we add a new version. Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33"` - // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. Relays (nodes with a valid NetAddress) are always Archival, regardless of this setting. This may change in the future. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only effects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. + // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only affects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. Archival bool `version[0]:"false"` // GossipFanout sets the maximum number of peers the node will connect to with outgoing connections. If the list of peers is less than this setting, fewer connections will be made. The node will not connect to the same peer multiple times (with outgoing connections). GossipFanout int `version[0]:"4"` - // NetAddress is the address and/or port on which the relay node listens for incoming connections, or blank to ignore incoming connections. Specify an IP and port or just a port. For example, 127.0.0.1:0 will listen on a random port on the localhost. + // NetAddress is the address and/or port on which a node listens for incoming connections, or blank to ignore incoming connections. Specify an IP and port or just a port. For example, 127.0.0.1:0 will listen on a random port on the localhost. NetAddress string `version[0]:""` // ReconnectTime is deprecated and unused. @@ -171,6 +171,7 @@ type Local struct { RestWriteTimeoutSeconds int `version[4]:"120"` // DNSBootstrapID specifies the names of a set of DNS SRV records that identify the set of nodes available to connect to. + // This is applicable to both relay and archival nodes - they are assumed to use the same DNSBootstrapID today. // When resolving the bootstrap ID will be replaced by the genesis block's network name. This string uses a URL // parsing library and supports optional backup and dedup parameters. 'backup' is used to provide a second DNS entry to use // in case the primary is unavailable. dedup is intended to be used to deduplicate SRV records returned from the primary @@ -454,28 +455,16 @@ type Local struct { // VerifiedTranscationsCacheSize defines the number of transactions that the verified transactions cache would hold before cycling the cache storage in a round-robin fashion. VerifiedTranscationsCacheSize int `version[14]:"30000" version[23]:"150000"` - // EnableCatchupFromArchiveServers controls which peers the catchup service would use in order to catchup. - // When enabled, the catchup service would use the archive servers before falling back to the relays. - // On networks that don't have archive servers, this becomes a no-op, as the catchup service would have no - // archive server to pick from, and therefore automatically selects one of the relay nodes. - EnableCatchupFromArchiveServers bool `version[15]:"false"` - // DisableLocalhostConnectionRateLimit controls whether the incoming connection rate limit would apply for // connections that are originating from the local machine. Setting this to "true", allow to create large // local-machine networks that won't trip the incoming connection limit observed by relays. DisableLocalhostConnectionRateLimit bool `version[16]:"true"` // BlockServiceCustomFallbackEndpoints is a comma delimited list of endpoints which the block service uses to - // redirect the http requests to in case it does not have the round. If it is not specified, will check - // EnableBlockServiceFallbackToArchiver. + // redirect the http requests to in case it does not have the round. If empty, the block service will return + // StatusNotFound (404) BlockServiceCustomFallbackEndpoints string `version[16]:""` - // EnableBlockServiceFallbackToArchiver controls whether the block service redirects the http requests to - // an archiver or return StatusNotFound (404) when in does not have the requested round, and - // BlockServiceCustomFallbackEndpoints is empty. - // The archiver is randomly selected, if none is available, will return StatusNotFound (404). - EnableBlockServiceFallbackToArchiver bool `version[16]:"true" version[31]:"false"` - // CatchupBlockValidateMode is a development and testing configuration used by the catchup service. // It can be used to omit certain validations to speed up the catchup process, or to apply extra validations which are redundant in normal operation. // This field is a bit-field with: diff --git a/config/local_defaults.go b/config/local_defaults.go index 791e6e2225..d2a73d4c6f 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -64,8 +64,6 @@ var defaultLocal = Local{ EnableAgreementTimeMetrics: false, EnableAssembleStats: false, EnableBlockService: false, - EnableBlockServiceFallbackToArchiver: false, - EnableCatchupFromArchiveServers: false, EnableDeveloperAPI: false, EnableExperimentalAPI: false, EnableFollowMode: false, diff --git a/config/version.go b/config/version.go index 272cff9f93..dce2bd533e 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 22 +const VersionMinor = 23 // Version is the type holding our full version information. type Version struct { diff --git a/go.mod b/go.mod index 8844a80585..0d85043883 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/libp2p/go-yamux/v4 v4.0.1 github.com/mattn/go-sqlite3 v1.14.16 github.com/miekg/dns v1.1.55 + github.com/muesli/termenv v0.15.2 github.com/multiformats/go-multiaddr v0.10.1 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olivere/elastic v6.2.14+incompatible @@ -51,6 +52,7 @@ require ( require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // 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 @@ -107,10 +109,12 @@ require ( 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/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -142,6 +146,7 @@ require ( 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/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1d4e5abf63..f8d0ac3a04 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 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= @@ -430,6 +432,8 @@ github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjP 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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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= @@ -451,6 +455,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky 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-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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= @@ -491,6 +497,8 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 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= @@ -591,6 +599,8 @@ github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2Ep 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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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= diff --git a/installer/config.json.example b/installer/config.json.example index aa1cb71712..d9188ef748 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -43,8 +43,6 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, - "EnableBlockServiceFallbackToArchiver": false, - "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, diff --git a/ledger/ledger.go b/ledger/ledger.go index fa1be1a766..110a72fc18 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -462,11 +462,25 @@ func (l *Ledger) notifyCommit(r basics.Round) basics.Round { if l.archival { // Do not forget any blocks. minToSave = 0 + } else { + catchpointsMinToSave := r.SubSaturate(l.calcMinCatchpointRoundsLookback()) + if catchpointsMinToSave < minToSave { + minToSave = catchpointsMinToSave + } } return minToSave } +func (l *Ledger) calcMinCatchpointRoundsLookback() basics.Round { + // cfg.StoresCatchpoints checks that CatchpointInterval is positive + if !l.cfg.StoresCatchpoints() || l.cfg.CatchpointFileHistoryLength == 0 { + return 0 + } + + return basics.Round(2 * l.cfg.CatchpointInterval) +} + // GetLastCatchpointLabel returns the latest catchpoint label that was written to the // database. func (l *Ledger) GetLastCatchpointLabel() string { @@ -901,7 +915,7 @@ func (l *Ledger) FlushCaches() { // Validate uses the ledger to validate block blk as a candidate next block. // It returns an error if blk is not the expected next block, or if blk is // not a valid block (e.g., it has duplicate transactions, overspends some -// account, etc). +// account, etc.). func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionPool execpool.BacklogPool) (*ledgercore.ValidatedBlock, error) { delta, err := eval.Eval(ctx, l, blk, true, l.verifiedTxnCache, executionPool, l.tracer) if err != nil { diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index ab5bc293a3..b18428741b 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -3287,3 +3287,67 @@ func TestLedgerMaxBlockHistoryLookback(t *testing.T) { require.Error(t, err) require.Empty(t, blk) } + +func TestLedgerRetainMinOffCatchpointInterval(t *testing.T) { + partitiontest.PartitionTest(t) + // This test is to ensure that the ledger retains the minimum number of blocks off the catchpoint interval. + blocksToMake := 2000 + + // Cases: + // 1. Base Case: Archival = false, Stores catchpoints returns true, CatchpointFileHistoryLength = >= 1 - implies catchpoint interval > 0 - min formula + // 2. Archival = true, stores catchpoints returns false - we keep all blocks anyway + // 3. Archival = false, stores catchpoints returns false - we don't modify minToSave + // 4. Condition: Archival = false, storesCatchpoints returns true, CatchpointFileHistoryLength is -1 - keep all catchpoint files + // 5. Condition: Archival = false, storesCatchpoints returns true, CatchpointFileHistoryLength is 365 - the config default setting + + catchpointIntervalBlockRetentionTestCases := []struct { + storeCatchpoints bool + archival bool + catchpointFileHistoryLength int + }{ + {true, false, 1}, // should use min catchpoint formula + {false, true, 1}, // all blocks get retained, archival mode dictates + {false, false, 1}, // should not modify min blocks retained based on catchpoint interval + {true, false, -1}, // should use min formula, this is the keep all catchpoints setting + {true, false, 365}, // should use min formula, this is the default setting for catchpoint file history length + } + for _, tc := range catchpointIntervalBlockRetentionTestCases { + func() { + var genHash crypto.Digest + crypto.RandBytes(genHash[:]) + cfg := config.GetDefaultLocal() + // set config properties based on test case + cfg.MaxBlockHistoryLookback = 0 // max block history lookback is not used in this test + if tc.storeCatchpoints { + cfg.CatchpointTracking = config.CatchpointTrackingModeStored + cfg.CatchpointInterval = 100 + } else { + cfg.CatchpointInterval = 0 // sufficient for cfg.StoresCatchpoints() to return false + } + cfg.CatchpointFileHistoryLength = tc.catchpointFileHistoryLength + cfg.Archival = tc.archival + + l := &Ledger{} + l.cfg = cfg + l.archival = cfg.Archival + + for i := 1; i <= blocksToMake; i++ { + minBlockToKeep := l.notifyCommit(basics.Round(i)) + + // In archival mode, all blocks should always be kept + if cfg.Archival { + require.Equal(t, basics.Round(0), minBlockToKeep) + } else { + // This happens to work for the test case where we don't store catchpoints since mintosave is always + // 0 in that case. + expectedCatchpointLookback := 2 * cfg.CatchpointInterval + + expectedMinBlockToKeep := basics.Round(uint64(i)).SubSaturate( + basics.Round(expectedCatchpointLookback)) + require.Equal(t, expectedMinBlockToKeep, minBlockToKeep) + } + } + }() + } + +} diff --git a/network/gossipNode.go b/network/gossipNode.go index 7a916fda36..3ac5cc7df0 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -42,8 +42,6 @@ const ( PeersPhonebookRelays PeerOption = iota // PeersPhonebookArchivalNodes specifies all archival nodes (relay or p2p) PeersPhonebookArchivalNodes PeerOption = iota - // PeersPhonebookArchivers specifies all archivers in the phonebook - PeersPhonebookArchivers PeerOption = iota ) // GossipNode represents a node in the gossip network diff --git a/network/phonebook.go b/network/phonebook.go index 3f196e0605..0c431fd2f3 100644 --- a/network/phonebook.go +++ b/network/phonebook.go @@ -30,7 +30,7 @@ import ( const getAllAddresses = math.MaxInt32 // PhoneBookEntryRoles defines the roles that a single entry on the phonebook can take. -// currently, we have two roles : relay role and archiver role, which are mutually exclusive. +// currently, we have two roles : relay role and archival role, which are mutually exclusive. // //msgp:ignore PhoneBookEntryRoles type PhoneBookEntryRoles int @@ -39,8 +39,8 @@ type PhoneBookEntryRoles int // or via a configuration file. const PhoneBookEntryRelayRole = 1 -// PhoneBookEntryArchiverRole used for all the archivers that are provided via the archive SRV record. -const PhoneBookEntryArchiverRole = 2 +// PhoneBookEntryArchivalRole used for all the archival nodes that are provided via the archive SRV record. +const PhoneBookEntryArchivalRole = 2 // Phonebook stores or looks up addresses of nodes we might contact type Phonebook interface { diff --git a/network/phonebook_test.go b/network/phonebook_test.go index 36365c5916..2643e722ea 100644 --- a/network/phonebook_test.go +++ b/network/phonebook_test.go @@ -346,11 +346,11 @@ func TestPhonebookRoles(t *testing.T) { ph := MakePhonebook(1, 1).(*phonebookImpl) ph.ReplacePeerList(relaysSet, "default", PhoneBookEntryRelayRole) - ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchiverRole) + ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchivalRole) require.Equal(t, len(relaysSet)+len(archiverSet), len(ph.data)) require.Equal(t, len(relaysSet)+len(archiverSet), ph.Length()) - for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} { + for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchivalRole} { for k := 0; k < 100; k++ { for l := 0; l < 3; l++ { entries := ph.GetAddresses(l, role) @@ -358,7 +358,7 @@ func TestPhonebookRoles(t *testing.T) { for _, entry := range entries { require.Contains(t, entry, "relay") } - } else if role == PhoneBookEntryArchiverRole { + } else if role == PhoneBookEntryArchivalRole { for _, entry := range entries { require.Contains(t, entry, "archiver") } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 9ce5f331ae..295bd71155 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -289,7 +289,7 @@ type WebsocketNetwork struct { protocolVersion string // resolveSRVRecords is a function that resolves SRV records for a given service, protocol and name - resolveSRVRecords func(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) + resolveSRVRecords func(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) } const ( @@ -548,15 +548,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { } case PeersPhonebookArchivalNodes: var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) - for _, addr := range addrs { - peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) - outPeers = append(outPeers, &peerCore) - } - case PeersPhonebookArchivers: - // return copy of phonebook, which probably also contains peers we're connected to, but if it doesn't maybe we shouldn't be making new connections to those peers (because they disappeared from the directory) - var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchiverRole) + addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchivalRole) for _, addr := range addrs { peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) outPeers = append(outPeers, &peerCore) @@ -1607,15 +1599,17 @@ func (wn *WebsocketNetwork) refreshRelayArchivePhonebookAddresses() { dnsBootstrapArray := wn.config.DNSBootstrapArray(wn.NetworkID) for _, dnsBootstrap := range dnsBootstrapArray { - primaryRelayAddrs, primaryArchiveAddrs := wn.getDNSAddrs(dnsBootstrap.PrimarySRVBootstrap) + primaryRelayAddrs, primaryArchivalAddrs := wn.getDNSAddrs(dnsBootstrap.PrimarySRVBootstrap) if dnsBootstrap.BackupSRVBootstrap != "" { - backupRelayAddrs, backupArchiveAddrs := wn.getDNSAddrs(dnsBootstrap.BackupSRVBootstrap) - dedupedRelayAddresses := wn.mergePrimarySecondaryRelayAddressSlices(wn.NetworkID, primaryRelayAddrs, + backupRelayAddrs, backupArchivalAddrs := wn.getDNSAddrs(dnsBootstrap.BackupSRVBootstrap) + dedupedRelayAddresses := wn.mergePrimarySecondaryAddressSlices(primaryRelayAddrs, backupRelayAddrs, dnsBootstrap.DedupExp) - wn.updatePhonebookAddresses(dedupedRelayAddresses, append(primaryArchiveAddrs, backupArchiveAddrs...)) + dedupedArchivalAddresses := wn.mergePrimarySecondaryAddressSlices(primaryArchivalAddrs, + backupArchivalAddrs, dnsBootstrap.DedupExp) + wn.updatePhonebookAddresses(dedupedRelayAddresses, dedupedArchivalAddresses) } else { - wn.updatePhonebookAddresses(primaryRelayAddrs, primaryArchiveAddrs) + wn.updatePhonebookAddresses(primaryRelayAddrs, primaryArchivalAddrs) } } } @@ -1628,7 +1622,9 @@ func (wn *WebsocketNetwork) updatePhonebookAddresses(relayAddrs []string, archiv wn.log.Infof("got no relay DNS addrs for network %s", wn.NetworkID) } if len(archiveAddrs) > 0 { - wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchiverRole) + wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchivalRole) + } else { + wn.log.Infof("got no archive DNS addrs for network %s", wn.NetworkID) } } @@ -1846,48 +1842,48 @@ func (wn *WebsocketNetwork) prioWeightRefresh() { } } -// This logic assumes that the relay address suffixes +// This logic assumes that the address suffixes // correspond to the primary/backup network conventions. If this proves to be false, i.e. one network's // suffix is a substring of another network's suffix, then duplicates can end up in the merged slice. -func (wn *WebsocketNetwork) mergePrimarySecondaryRelayAddressSlices(network protocol.NetworkID, - primaryRelayAddresses []string, secondaryRelayAddresses []string, dedupExp *regexp.Regexp) (dedupedRelayAddresses []string) { +func (wn *WebsocketNetwork) mergePrimarySecondaryAddressSlices( + primaryAddresses []string, secondaryAddresses []string, dedupExp *regexp.Regexp) (dedupedAddresses []string) { if dedupExp == nil { // No expression provided, so just append the slices without deduping - return append(primaryRelayAddresses, secondaryRelayAddresses...) + return append(primaryAddresses, secondaryAddresses...) } - var relayAddressPrefixToValue = make(map[string]string, 2*len(primaryRelayAddresses)) + var addressPrefixToValue = make(map[string]string, 2*len(primaryAddresses)) - for _, pra := range primaryRelayAddresses { + for _, pra := range primaryAddresses { var normalizedPra = strings.ToLower(pra) var pfxKey = dedupExp.ReplaceAllString(normalizedPra, "") - if _, exists := relayAddressPrefixToValue[pfxKey]; !exists { - relayAddressPrefixToValue[pfxKey] = normalizedPra + if _, exists := addressPrefixToValue[pfxKey]; !exists { + addressPrefixToValue[pfxKey] = normalizedPra } } - for _, sra := range secondaryRelayAddresses { + for _, sra := range secondaryAddresses { var normalizedSra = strings.ToLower(sra) var pfxKey = dedupExp.ReplaceAllString(normalizedSra, "") - if _, exists := relayAddressPrefixToValue[pfxKey]; !exists { - relayAddressPrefixToValue[pfxKey] = normalizedSra + if _, exists := addressPrefixToValue[pfxKey]; !exists { + addressPrefixToValue[pfxKey] = normalizedSra } } - dedupedRelayAddresses = make([]string, 0, len(relayAddressPrefixToValue)) - for _, value := range relayAddressPrefixToValue { - dedupedRelayAddresses = append(dedupedRelayAddresses, value) + dedupedAddresses = make([]string, 0, len(addressPrefixToValue)) + for _, value := range addressPrefixToValue { + dedupedAddresses = append(dedupedAddresses, value) } return } -func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archiverAddresses []string) { +func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archivalAddresses []string) { var err error - relaysAddresses, err = wn.resolveSRVRecords("algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + relaysAddresses, err = wn.resolveSRVRecords(wn.ctx, "algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { // only log this warning on testnet or devnet if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { @@ -1895,15 +1891,14 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses [] } relaysAddresses = nil } - if wn.config.EnableCatchupFromArchiveServers || wn.config.EnableBlockServiceFallbackToArchiver { - archiverAddresses, err = wn.resolveSRVRecords("archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) - if err != nil { - // only log this warning on testnet or devnet - if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { - wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) - } - archiverAddresses = nil + + archivalAddresses, err = wn.resolveSRVRecords(wn.ctx, "archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + if err != nil { + // only log this warning on testnet or devnet + if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { + wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) } + archivalAddresses = nil } return } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index b7cd873bcf..8daf4d196f 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1183,6 +1183,9 @@ func TestGetPeers(t *testing.T) { phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", PhoneBookEntryRelayRole) + // A few for archival node roles + phbMulti.ReplacePeerList([]string{"d", "e", "f"}, "ph", PhoneBookEntryArchivalRole) + //addrB, _ := netB.Address() // A has only an inbound connection from B @@ -1206,14 +1209,13 @@ func TestGetPeers(t *testing.T) { sort.Strings(expectAddrs) assert.Equal(t, expectAddrs, peerAddrs) - // For now, PeersPhonebookArchivalNodes and PeersPhonebookRelays will return the same set of nodes bPeers2 := netB.GetPeers(PeersPhonebookArchivalNodes) peerAddrs2 := make([]string, len(bPeers2)) for pi2, peer2 := range bPeers2 { peerAddrs2[pi2] = peer2.(HTTPPeer).GetAddress() } sort.Strings(peerAddrs2) - assert.Equal(t, expectAddrs, peerAddrs2) + assert.Equal(t, []string{"d", "e", "f"}, peerAddrs2) } @@ -4127,95 +4129,82 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { var netA *WebsocketNetwork var refreshRelayDNSBootstrapID = ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)" - testRefreshWithConfig := func(refreshTestConf config.Local) { - rapid.Check(t, func(t1 *rapid.T) { - refreshTestConf.DNSBootstrapID = refreshRelayDNSBootstrapID - netA = makeTestWebsocketNodeWithConfig(t, refreshTestConf) - netA.NetworkID = nonHardcodedNetworkIDGen().Draw(t1, "network") - - primarySRVBootstrap := strings.Replace(".algorand.network", "", string(netA.NetworkID), -1) - backupSRVBootstrap := strings.Replace(".algorand.net", "", string(netA.NetworkID), -1) - var primaryRelayResolvedRecords []string - var secondaryRelayResolvedRecords []string - var primaryArchiveResolvedRecords []string - var secondaryArchiveResolvedRecords []string - - for _, record := range []string{"r1.algorand-.network", - "r2.algorand-.network", "r3.algorand-.network"} { - var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) - primaryRelayResolvedRecords = append(primaryRelayResolvedRecords, recordSub) - secondaryRelayResolvedRecords = append(secondaryRelayResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) - } + refreshTestConf := defaultConfig - for _, record := range []string{"r1archive.algorand-.network", - "r2archive.algorand-.network", "r3archive.algorand-.network"} { - var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) - primaryArchiveResolvedRecords = append(primaryArchiveResolvedRecords, recordSub) - secondaryArchiveResolvedRecords = append(secondaryArchiveResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) - } - - // Mock the SRV record lookup - netA.resolveSRVRecords = func(service string, protocol string, name string, fallbackDNSResolverAddress string, - secure bool) (addrs []string, err error) { - if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { - return primaryRelayResolvedRecords, nil - } else if service == "algobootstrap" && protocol == "tcp" && name == backupSRVBootstrap { - return secondaryRelayResolvedRecords, nil - } + rapid.Check(t, func(t1 *rapid.T) { + refreshTestConf.DNSBootstrapID = refreshRelayDNSBootstrapID + netA = makeTestWebsocketNodeWithConfig(t, refreshTestConf) + netA.NetworkID = nonHardcodedNetworkIDGen().Draw(t1, "network") + + primarySRVBootstrap := strings.Replace(".algorand.network", "", string(netA.NetworkID), -1) + backupSRVBootstrap := strings.Replace(".algorand.net", "", string(netA.NetworkID), -1) + var primaryRelayResolvedRecords []string + var secondaryRelayResolvedRecords []string + var primaryArchiveResolvedRecords []string + var secondaryArchiveResolvedRecords []string + + for _, record := range []string{"r1.algorand-.network", + "r2.algorand-.network", "r3.algorand-.network"} { + var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) + primaryRelayResolvedRecords = append(primaryRelayResolvedRecords, recordSub) + secondaryRelayResolvedRecords = append(secondaryRelayResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) + } - if service == "archive" && protocol == "tcp" && name == primarySRVBootstrap { - return primaryArchiveResolvedRecords, nil - } else if service == "archive" && protocol == "tcp" && name == backupSRVBootstrap { - return secondaryArchiveResolvedRecords, nil - } + for _, record := range []string{"r1archive.algorand-.network", + "r2archive.algorand-.network", "r3archive.algorand-.network"} { + var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) + primaryArchiveResolvedRecords = append(primaryArchiveResolvedRecords, recordSub) + secondaryArchiveResolvedRecords = append(secondaryArchiveResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) + } - return + // Mock the SRV record lookup + netA.resolveSRVRecords = func(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, + secure bool) (addrs []string, err error) { + if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { + return primaryRelayResolvedRecords, nil + } else if service == "algobootstrap" && protocol == "tcp" && name == backupSRVBootstrap { + return secondaryRelayResolvedRecords, nil } - relayPeers := netA.GetPeers(PeersPhonebookRelays) - assert.Equal(t, 0, len(relayPeers)) - - archivePeers := netA.GetPeers(PeersPhonebookArchivers) - assert.Equal(t, 0, len(archivePeers)) - - netA.refreshRelayArchivePhonebookAddresses() + if service == "archive" && protocol == "tcp" && name == primarySRVBootstrap { + return primaryArchiveResolvedRecords, nil + } else if service == "archive" && protocol == "tcp" && name == backupSRVBootstrap { + return secondaryArchiveResolvedRecords, nil + } - relayPeers = netA.GetPeers(PeersPhonebookRelays) + return + } - assert.Equal(t, 3, len(relayPeers)) - relayAddrs := make([]string, 0, len(relayPeers)) - for _, peer := range relayPeers { - relayAddrs = append(relayAddrs, peer.(HTTPPeer).GetAddress()) - } + relayPeers := netA.GetPeers(PeersPhonebookRelays) + assert.Equal(t, 0, len(relayPeers)) - assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) + archivePeers := netA.GetPeers(PeersPhonebookArchivalNodes) + assert.Equal(t, 0, len(archivePeers)) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + netA.refreshRelayArchivePhonebookAddresses() - if refreshTestConf.EnableBlockServiceFallbackToArchiver { - // For the time being, we do not dedup resolved archive nodes - assert.Equal(t, len(primaryArchiveResolvedRecords)+len(secondaryArchiveResolvedRecords), len(archivePeers)) + relayPeers = netA.GetPeers(PeersPhonebookRelays) - archiveAddrs := make([]string, 0, len(archivePeers)) - for _, peer := range archivePeers { - archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) - } + assert.Equal(t, 3, len(relayPeers)) + relayAddrs := make([]string, 0, len(relayPeers)) + for _, peer := range relayPeers { + relayAddrs = append(relayAddrs, peer.(HTTPPeer).GetAddress()) + } - assert.ElementsMatch(t, append(primaryArchiveResolvedRecords, secondaryArchiveResolvedRecords...), archiveAddrs) + assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) - } else { - assert.Equal(t, 0, len(archivePeers)) - } + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) - }) - } + assert.Equal(t, 3, len(archivePeers)) - testRefreshWithConfig(defaultConfig) + archiveAddrs := make([]string, 0, len(archivePeers)) + for _, peer := range archivePeers { + archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) + } - configWithBlockServiceFallbackToArchiverEnabled := config.GetDefaultLocal() - configWithBlockServiceFallbackToArchiverEnabled.EnableBlockServiceFallbackToArchiver = true + assert.ElementsMatch(t, primaryArchiveResolvedRecords, archiveAddrs) - testRefreshWithConfig(configWithBlockServiceFallbackToArchiverEnabled) + }) } /* @@ -4231,7 +4220,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { relayPeers := netA.GetPeers(PeersPhonebookRelays) assert.Equal(t, 0, len(relayPeers)) - archivePeers := netA.GetPeers(PeersPhonebookArchivers) + archivePeers := netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, 0, len(archivePeers)) domainGen := rapidgen.Domain() @@ -4260,7 +4249,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, dedupedRelayDomains, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, len(dedupedArchiveDomains), len(archivePeers)) archiveAddrs := make([]string, 0, len(archivePeers)) @@ -4300,7 +4289,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, dedupedRelayDomains, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, len(dedupedArchiveDomains), len(archivePeers)) archiveAddrs = nil @@ -4361,7 +4350,7 @@ func TestMergePrimarySecondaryRelayAddressListsMinOverlap(t *testing.T) { primaryRelayAddresses := domainsGen.Draw(t1, "primaryRelayAddresses") secondaryRelayAddresses := domainsGen.Draw(t1, "secondaryRelayAddresses") - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(protocol.NetworkID(network), + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, dedupExp) expectedRelayAddresses := removeDuplicateStr(append(primaryRelayAddresses, secondaryRelayAddresses...), true) @@ -4414,7 +4403,7 @@ func TestMergePrimarySecondaryRelayAddressListsPartialOverlap(t *testing.T) { } secondaryRelayAddresses = append(secondaryRelayAddresses, extraSecondaryRelayAddresses...) - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(network, + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, dedupExp) // We expect the primary addresses to take precedence over a "matching" secondary address, extra non-duplicate @@ -4457,7 +4446,7 @@ func TestMergePrimarySecondaryRelayAddressListsNoDedupExp(t *testing.T) { generatedSecondaryRelayAddresses := secondaryDomainsGen.Draw(t1, "secondaryRelayAddresses") secondaryRelayAddresses = append(secondaryRelayAddresses, generatedSecondaryRelayAddresses...) - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(protocol.NetworkID(network), + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, nil) // We expect non deduplication, so all addresses _should_ be present (note that no lower casing happens either) diff --git a/node/follower_node.go b/node/follower_node.go index 277c48a24e..8483f14679 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -127,6 +127,11 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo } node.ledger.RegisterBlockListeners(blockListeners) + + if cfg.IsGossipServer() { + rpcs.MakeHealthService(node.net) + } + node.blockService = rpcs.MakeBlockService(node.log, cfg, node.ledger, p2pNode, node.genesisID) node.catchupBlockAuth = blockAuthenticatorImpl{Ledger: node.ledger, AsyncVoteVerifier: agreement.MakeAsyncVoteVerifier(node.lowPriorityCryptoVerificationPool)} node.catchupService = catchup.MakeService(node.log, node.config, p2pNode, node.ledger, node.catchupBlockAuth, make(chan catchup.PendingUnmatchedCertificate), node.lowPriorityCryptoVerificationPool) diff --git a/node/node.go b/node/node.go index f59bd67dab..53c6c492a9 100644 --- a/node/node.go +++ b/node/node.go @@ -251,6 +251,11 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd return nil, err } + // The health service registers itself with the network + if cfg.IsGossipServer() { + rpcs.MakeHealthService(node.net) + } + node.blockService = rpcs.MakeBlockService(node.log, cfg, node.ledger, p2pNode, node.genesisID) node.ledgerService = rpcs.MakeLedgerService(cfg, node.ledger, p2pNode, node.genesisID) rpcs.RegisterTxService(node.transactionPool, p2pNode, node.genesisID, cfg.TxPoolSize, cfg.TxSyncServeResponseSize) diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 7245eb1881..8231b5a98b 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -36,7 +36,6 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -103,7 +102,6 @@ type BlockService struct { enableService bool enableServiceOverGossip bool fallbackEndpoints fallbackEndpoints - enableArchiverFallback bool log logging.Logger closeWaitGroup sync.WaitGroup mu deadlock.Mutex @@ -144,7 +142,6 @@ func MakeBlockService(log logging.Logger, config config.Local, ledger LedgerForB enableService: config.EnableBlockService, enableServiceOverGossip: config.EnableGossipBlockService, fallbackEndpoints: makeFallbackEndpoints(log, config.BlockServiceCustomFallbackEndpoints), - enableArchiverFallback: config.EnableBlockServiceFallbackToArchiver, log: log, memoryCap: config.BlockServiceMemCap, } @@ -384,13 +381,10 @@ func (bs *BlockService) handleCatchupReq(ctx context.Context, reqMsg network.Inc return } -// redirectRequest redirects the request to the next round robin fallback endpoing if available, otherwise, -// if EnableBlockServiceFallbackToArchiver is enabled, redirects to a random archiver. +// redirectRequest redirects the request to the next round robin fallback endpoint if available func (bs *BlockService) redirectRequest(round uint64, response http.ResponseWriter, request *http.Request) (ok bool) { peerAddress := bs.getNextCustomFallbackEndpoint() - if peerAddress == "" && bs.enableArchiverFallback { - peerAddress = bs.getRandomArchiver() - } + if peerAddress == "" { return false } @@ -411,30 +405,14 @@ func (bs *BlockService) getNextCustomFallbackEndpoint() (endpointAddress string) if len(bs.fallbackEndpoints.endpoints) == 0 { return } + + bs.mu.Lock() + defer bs.mu.Unlock() endpointAddress = bs.fallbackEndpoints.endpoints[bs.fallbackEndpoints.lastUsed] bs.fallbackEndpoints.lastUsed = (bs.fallbackEndpoints.lastUsed + 1) % len(bs.fallbackEndpoints.endpoints) return } -// getRandomArchiver returns a random archiver address -func (bs *BlockService) getRandomArchiver() (endpointAddress string) { - peers := bs.net.GetPeers(network.PeersPhonebookArchivers) - httpPeers := make([]network.HTTPPeer, 0, len(peers)) - - for _, peer := range peers { - httpPeer, validHTTPPeer := peer.(network.HTTPPeer) - if validHTTPPeer { - httpPeers = append(httpPeers, httpPeer) - } - } - if len(httpPeers) == 0 { - return - } - randIndex := crypto.RandUint64() % uint64(len(httpPeers)) - endpointAddress = httpPeers[randIndex].GetAddress() - return -} - // rawBlockBytes returns the block/cert for a given round, while taking the lock // to ensure the block service is currently active. func (bs *BlockService) rawBlockBytes(round basics.Round) ([]byte, error) { diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index e77fc7aa0e..3aab7c4abb 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -125,8 +125,9 @@ func TestHandleCatchupReqNegative(t *testing.T) { require.Equal(t, roundNumberParseErrMsg, string(val)) } -// TestRedirectFallbackArchiver tests the case when the block service fallback to another in the absence of a given block. -func TestRedirectFallbackArchiver(t *testing.T) { +// TestRedirectFallbackEndpoints tests the case when the block service falls back to another from +// BlockServiceCustomFallbackEndpoints in the absence of a given block. +func TestRedirectFallbackEndpoints(t *testing.T) { partitiontest.PartitionTest(t) log := logging.TestingLog(t) @@ -142,25 +143,23 @@ func TestRedirectFallbackArchiver(t *testing.T) { net1 := &httpTestPeerSource{} 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") - nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeA.start() defer nodeA.stop() - - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) nodeB.start() defer nodeB.stop() - net1.addPeer(nodeB.rootURL()) + config := config.GetDefaultLocal() + // Set the first to a bad address, the second to self, and the third to the one that has the block. + // If RR is right, should succeed. + config.BlockServiceCustomFallbackEndpoints = fmt.Sprintf("://badaddress,%s,%s", nodeA.rootURL(), nodeB.rootURL()) + + bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID") + bs2 := MakeBlockService(log, config, ledger2, net2, "test-genesis-ID") + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -235,60 +234,8 @@ func TestBlockServiceShutdown(t *testing.T) { <-requestDone } -// TestRedirectBasic tests the case when the block service redirects the request to elsewhere -func TestRedirectFallbackEndpoints(t *testing.T) { - partitiontest.PartitionTest(t) - - log := logging.TestingLog(t) - - ledger1 := makeLedger(t, "l1") - defer ledger1.Close() - ledger2 := makeLedger(t, "l2") - defer ledger2.Close() - addBlock(t, ledger2) - - net1 := &httpTestPeerSource{} - net2 := &httpTestPeerSource{} - - nodeA := &basicRPCNode{} - nodeB := &basicRPCNode{} - nodeA.start() - defer nodeA.stop() - nodeB.start() - defer nodeB.stop() - - config := config.GetDefaultLocal() - // Set the first to a bad address, the second to self, and the third to the one that has the block. - // If RR is right, should succeed. - config.BlockServiceCustomFallbackEndpoints = fmt.Sprintf("://badaddress,%s,%s", nodeA.rootURL(), nodeB.rootURL()) - bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}") - bs2 := MakeBlockService(log, config, ledger2, net2, "{genesisID}") - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) - require.NoError(t, err) - - client := http.Client{} - - ctx := context.Background() - parsedURL.Path = FormatBlockQuery(uint64(1), parsedURL.Path, net1) - blockURL := parsedURL.String() - request, err := http.NewRequest("GET", blockURL, nil) - require.NoError(t, err) - requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(config.CatchupHTTPBlockFetchTimeoutSec)*time.Second) - defer requestCancel() - request = request.WithContext(requestCtx) - network.SetUserAgentHeader(request.Header) - response, err := client.Do(request) - require.NoError(t, err) - - require.Equal(t, http.StatusOK, response.StatusCode) -} - -// TestRedirectFallbackArchiver tests the case when the block service -// fallback to another because its memory use it at capacity +// TestRedirectOnFullCapacity tests the case when the block service +// fallback to another because its memory use is at capacity func TestRedirectOnFullCapacity(t *testing.T) { partitiontest.PartitionTest(t) @@ -313,27 +260,31 @@ func TestRedirectOnFullCapacity(t *testing.T) { net1 := &httpTestPeerSource{} 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 - bs1.memoryCap = 250 - bs2.memoryCap = 250 - nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeA.start() defer nodeA.stop() - - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) nodeB.start() defer nodeB.stop() - net1.addPeer(nodeB.rootURL()) + configWithRedirects := config.GetDefaultLocal() + + configWithRedirects.BlockServiceCustomFallbackEndpoints = nodeB.rootURL() + + bs1 := MakeBlockService(log1, configWithRedirects, ledger1, net1, "test-genesis-ID") + + // config with no redirects + configNoRedirects := config.GetDefaultLocal() + configNoRedirects.BlockServiceCustomFallbackEndpoints = "" + + bs2 := MakeBlockService(log2, configNoRedirects, ledger2, net2, "test-genesis-ID") + // set the memory cap so that it can serve only 1 block at a time + bs1.memoryCap = 250 + bs2.memoryCap = 250 + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -483,27 +434,36 @@ func TestWsBlockLimiting(t *testing.T) { func TestRedirectExceptions(t *testing.T) { partitiontest.PartitionTest(t) - log := logging.TestingLog(t) + log1 := logging.TestingLog(t) + log2 := logging.TestingLog(t) ledger1 := makeLedger(t, "l1") + ledger2 := makeLedger(t, "l2") defer ledger1.Close() + defer ledger2.Close() addBlock(t, ledger1) net1 := &httpTestPeerSource{} - - config := config.GetDefaultLocal() - // Need to enable block service fallbacks - config.EnableBlockServiceFallbackToArchiver = true - - bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}") + net2 := &httpTestPeerSource{} nodeA := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB := &basicRPCNode{} nodeA.start() defer nodeA.stop() + nodeB.start() + defer nodeB.stop() + + configInvalidRedirects := config.GetDefaultLocal() + configInvalidRedirects.BlockServiceCustomFallbackEndpoints = "badAddress" + + configWithRedirectToSelf := config.GetDefaultLocal() + configWithRedirectToSelf.BlockServiceCustomFallbackEndpoints = nodeB.rootURL() - net1.peers = append(net1.peers, "invalidPeer") + bs1 := MakeBlockService(log1, configInvalidRedirects, ledger1, net1, "{genesisID}") + bs2 := MakeBlockService(log2, configWithRedirectToSelf, ledger2, net2, "{genesisID}") + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -515,7 +475,7 @@ func TestRedirectExceptions(t *testing.T) { blockURL := parsedURL.String() request, err := http.NewRequest("GET", blockURL, nil) require.NoError(t, err) - requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(config.CatchupHTTPBlockFetchTimeoutSec)*time.Second) + requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(configInvalidRedirects.CatchupHTTPBlockFetchTimeoutSec)*time.Second) defer requestCancel() request = request.WithContext(requestCtx) network.SetUserAgentHeader(request.Header) @@ -524,8 +484,14 @@ func TestRedirectExceptions(t *testing.T) { require.NoError(t, err) require.Equal(t, response.StatusCode, http.StatusNotFound) - net1.addPeer(nodeA.rootURL()) - _, err = client.Do(request) + parsedURLNodeB, err := network.ParseHostOrURL(nodeB.rootURL()) + require.NoError(t, err) + + parsedURLNodeB.Path = FormatBlockQuery(uint64(4), parsedURLNodeB.Path, net2) + blockURLNodeB := parsedURLNodeB.String() + requestNodeB, err := http.NewRequest("GET", blockURLNodeB, nil) + _, err = client.Do(requestNodeB) + require.Error(t, err) require.Contains(t, err.Error(), "stopped after 10 redirects") } diff --git a/rpcs/healthService.go b/rpcs/healthService.go new file mode 100644 index 0000000000..d3121d8bd2 --- /dev/null +++ b/rpcs/healthService.go @@ -0,0 +1,41 @@ +// Copyright (C) 2019-2024 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 rpcs + +import ( + "github.com/algorand/go-algorand/network" + "net/http" +) + +// HealthServiceStatusPath is the path to register HealthService as a handler for when using gorilla/mux +const HealthServiceStatusPath = "/status" + +// HealthService is a service that provides health information endpoints for the node +type HealthService struct{} + +// MakeHealthService creates a new HealthService and registers it with the provided network if enabled +func MakeHealthService(net network.GossipNode) HealthService { + service := HealthService{} + + net.RegisterHTTPHandler(HealthServiceStatusPath, service) + + return service +} + +func (h HealthService) ServeHTTP(writer http.ResponseWriter, _ *http.Request) { + writer.WriteHeader(http.StatusOK) +} diff --git a/rpcs/healthService_test.go b/rpcs/healthService_test.go new file mode 100644 index 0000000000..9d0bb215c2 --- /dev/null +++ b/rpcs/healthService_test.go @@ -0,0 +1,52 @@ +// Copyright (C) 2019-2024 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 rpcs + +import ( + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "io" + "net/http" + "path" + "testing" +) + +func TestHealthService_ServeHTTP(t *testing.T) { + partitiontest.PartitionTest(t) + + nodeA := &basicRPCNode{} + nodeA.start() + defer nodeA.stop() + + _ = MakeHealthService(nodeA) + + parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + require.NoError(t, err) + + client := http.Client{} + + parsedURL.Path = path.Join(parsedURL.Path, HealthServiceStatusPath) + + response, err := client.Get(parsedURL.String()) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode) + bodyData, err := io.ReadAll(response.Body) + require.NoError(t, err) + require.Empty(t, bodyData) +} diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh index 10bdb8630d..13dfca5bf1 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.7 +BUILD=1.20.14 MIN=1.20 GO_MOD_SUPPORT=1.20 diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt index b46aead08e..d8ee41d7e6 100644 --- a/test/heapwatch/requirements.txt +++ b/test/heapwatch/requirements.txt @@ -1,6 +1,6 @@ dash==2.11.1 dash-table==5.0.0 -Jinja2==3.1.2 +Jinja2==3.1.3 matplotlib==3.7.2 plotly==5.16.0 py-algorand-sdk==2.3.0 diff --git a/test/testdata/configs/config-v33.json b/test/testdata/configs/config-v33.json index aa1cb71712..d9188ef748 100644 --- a/test/testdata/configs/config-v33.json +++ b/test/testdata/configs/config-v33.json @@ -43,8 +43,6 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, - "EnableBlockServiceFallbackToArchiver": false, - "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, diff --git a/tools/network/bootstrap.go b/tools/network/bootstrap.go index f04c67528c..d30ae4bda0 100644 --- a/tools/network/bootstrap.go +++ b/tools/network/bootstrap.go @@ -24,7 +24,7 @@ import ( "github.com/algorand/go-algorand/logging" ) -func readFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (records []*net.SRV, err error) { +func readFromSRV(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (records []*net.SRV, err error) { log := logging.Base() if name == "" { log.Debug("no dns lookup due to empty name") @@ -38,14 +38,14 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv controller := NewResolveController(secure, fallbackDNSResolverAddress, log) systemResolver := controller.SystemResolver() - _, records, sysLookupErr := systemResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, sysLookupErr := systemResolver.LookupSRV(ctx, service, protocol, name) if sysLookupErr != nil { log.Infof("ReadFromBootstrap: DNS LookupSRV failed when using system resolver: %v", sysLookupErr) var fallbackLookupErr error if fallbackDNSResolverAddress != "" { fallbackResolver := controller.FallbackResolver() - _, records, fallbackLookupErr = fallbackResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, fallbackLookupErr = fallbackResolver.LookupSRV(ctx, service, protocol, name) } if fallbackLookupErr != nil { log.Infof("ReadFromBootstrap: DNS LookupSRV failed when using fallback '%s' resolver: %v", fallbackDNSResolverAddress, fallbackLookupErr) @@ -54,7 +54,7 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv if fallbackLookupErr != nil || fallbackDNSResolverAddress == "" { fallbackResolver := controller.DefaultResolver() var defaultLookupErr error - _, records, defaultLookupErr = fallbackResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, defaultLookupErr = fallbackResolver.LookupSRV(ctx, service, protocol, name) if defaultLookupErr != nil { err = fmt.Errorf("ReadFromBootstrap: DNS LookupSRV failed when using system resolver(%v), fallback resolver(%v), as well as using default resolver due to %v", sysLookupErr, fallbackLookupErr, defaultLookupErr) return @@ -65,8 +65,8 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv } // ReadFromSRV is a helper to collect SRV addresses for a given name -func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { - records, err := readFromSRV(service, protocol, name, fallbackDNSResolverAddress, secure) +func ReadFromSRV(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { + records, err := readFromSRV(ctx, service, protocol, name, fallbackDNSResolverAddress, secure) if err != nil { return addrs, err } @@ -88,7 +88,7 @@ func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolv // ReadFromSRVPriority is a helper to collect SRV addresses with priorities for a given name func ReadFromSRVPriority(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (prioAddrs map[uint16][]string, err error) { - records, err := readFromSRV(service, protocol, name, fallbackDNSResolverAddress, secure) + records, err := readFromSRV(context.Background(), service, protocol, name, fallbackDNSResolverAddress, secure) if err != nil { return prioAddrs, err } diff --git a/tools/network/bootstrap_test.go b/tools/network/bootstrap_test.go index 155615c704..a24bea422e 100644 --- a/tools/network/bootstrap_test.go +++ b/tools/network/bootstrap_test.go @@ -17,6 +17,7 @@ package network import ( + "context" "testing" "github.com/algorand/go-algorand/test/partitiontest" @@ -55,10 +56,10 @@ func TestReadFromSRV(t *testing.T) { fallback := "" secure := true - addrs, err := ReadFromSRV("", protocol, name, fallback, secure) + addrs, err := ReadFromSRV(context.Background(), "", protocol, name, fallback, secure) require.Error(t, err) - addrs, err = ReadFromSRV(service, protocol, name, fallback, secure) + addrs, err = ReadFromSRV(context.Background(), service, protocol, name, fallback, secure) require.NoError(t, err) require.GreaterOrEqual(t, len(addrs), 1) addr := addrs[0] diff --git a/tools/network/telemetryURIUpdateService.go b/tools/network/telemetryURIUpdateService.go index 66dd87dd0e..2b4e614261 100644 --- a/tools/network/telemetryURIUpdateService.go +++ b/tools/network/telemetryURIUpdateService.go @@ -17,6 +17,7 @@ package network import ( + "context" "net/url" "strings" "time" @@ -132,5 +133,5 @@ func (t *telemetryURIUpdater) lookupTelemetryURL() (url *url.URL) { } func (t *telemetryURIUpdater) readFromSRV(protocol string, bootstrapID string) (addrs []string, err error) { - return ReadFromSRV("telemetry", protocol, bootstrapID, t.cfg.FallbackDNSResolverAddress, t.cfg.DNSSecuritySRVEnforced()) + return ReadFromSRV(context.Background(), "telemetry", protocol, bootstrapID, t.cfg.FallbackDNSResolverAddress, t.cfg.DNSSecuritySRVEnforced()) } From dd22cff42da3eef29dd1c3f6965198fde9a492a1 Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 14 Mar 2024 11:46:54 -0400 Subject: [PATCH 091/218] Docker: Bump debian version in algod container (#5955) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c6802506af..48bc652dcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,7 +41,7 @@ RUN /dist/files/build/install.sh \ -b "${BRANCH}" \ -s "${SHA}" -FROM debian:bookworm-20230703-slim as final +FROM debian:bookworm-20240311-slim as final ENV PATH="/node/bin:${PATH}" ALGOD_PORT="8080" KMD_PORT="7833" ALGORAND_DATA="/algod/data" From 98ac36a21232294ae984077665fbfb92940f87da Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Fri, 15 Mar 2024 19:46:46 -0400 Subject: [PATCH 092/218] Network: Penalize peer/class rankings when peer does not have catchpoint (#5959) --- catchup/catchpointService.go | 2 ++ catchup/peerSelector.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 3c11d0db73..3954c1cd6c 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -824,6 +824,8 @@ func (cs *CatchpointCatchupService) checkLedgerDownload() error { if err == nil { return nil } + // a non-nil error means that the catchpoint is not available, so we should rank it accordingly + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankNoCatchpointForRound) } return fmt.Errorf("checkLedgerDownload(): catchpoint '%s' unavailable from peers: %s", cs.stats.CatchpointLabel, err) } diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 05556bb24b..a8eefb0958 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -55,6 +55,10 @@ const ( // This indicates a peer is either behind or a block has not happened yet, or does not have a block that is old enough. peerRankNoBlockForRound = 2000 + // peerRankNoCatchpointForRound is used for responses failed because of no catchpoint for round + // This indicates a peer is either behind or a catchpoint has not been produced, or this node did not retain this catchpoint (aged out). + peerRankNoCatchpointForRound = 2000 + // peerRankDownloadFailed is used for responses which could be temporary, such as missing files, or such that we don't // have clear resolution peerRankDownloadFailed = 10000 From ef9a85766008245a2fdb87fac1811111b0b146ff Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 15 Mar 2024 20:27:45 -0400 Subject: [PATCH 093/218] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +1 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885 From 2ca765b488a5329b45eb530a6732dc03d1aa1c4b Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Wed, 20 Mar 2024 14:58:06 +0000 Subject: [PATCH 094/218] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index d00491fd7e..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/config/version.go b/config/version.go index dce2bd533e..643ac34e86 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 23 +const VersionMinor = 24 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From 9a4c08874a5ceee496b6027db009125ad0a44428 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:22:59 -0400 Subject: [PATCH 095/218] catchpoint: fix data files deletion in hot/cold dirs scenario (#5963) --- ledger/catchpointtracker.go | 3 +- ledger/catchpointtracker_test.go | 214 ++++++++++++++++++------------- 2 files changed, 125 insertions(+), 92 deletions(-) diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index b50a0d2d1e..036f5490b3 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -818,7 +818,6 @@ func (ct *catchpointTracker) createCatchpoint(ctx context.Context, accountsRound } relCatchpointFilePath := filepath.Join(trackerdb.CatchpointDirName, trackerdb.MakeCatchpointFilePath(round)) - absCatchpointFilePath := filepath.Join(ct.dbDirectory, relCatchpointFilePath) err = os.MkdirAll(filepath.Dir(absCatchpointFilePath), 0700) @@ -929,7 +928,7 @@ func (ct *catchpointTracker) pruneFirstStageRecordsData(ctx context.Context, max for _, round := range rounds { relCatchpointDataFilePath := filepath.Join(trackerdb.CatchpointDirName, makeCatchpointDataFilePath(round)) - err = trackerdb.RemoveSingleCatchpointFileFromDisk(ct.dbDirectory, relCatchpointDataFilePath) + err = trackerdb.RemoveSingleCatchpointFileFromDisk(ct.tmpDir, relCatchpointDataFilePath) if err != nil { return err } diff --git a/ledger/catchpointtracker_test.go b/ledger/catchpointtracker_test.go index 60f7366c74..6790344889 100644 --- a/ledger/catchpointtracker_test.go +++ b/ledger/catchpointtracker_test.go @@ -63,19 +63,23 @@ func TestCatchpointIsWritingCatchpointFile(t *testing.T) { } func newCatchpointTracker(tb testing.TB, l *mockLedgerForTracker, conf config.Local, dbPathPrefix string) *catchpointTracker { + return newCatchpointTrackerWithPaths(tb, l, conf, dbPathPrefix, dbPathPrefix) +} + +func newCatchpointTrackerWithPaths(tb testing.TB, l *mockLedgerForTracker, conf config.Local, hotPath string, coldPath string) *catchpointTracker { au := &accountUpdates{} ct := &catchpointTracker{} ao := &onlineAccounts{} au.initialize(conf) paths := DirsAndPrefix{ ResolvedGenesisDirs: config.ResolvedGenesisDirs{ - CatchpointGenesisDir: dbPathPrefix, - HotGenesisDir: dbPathPrefix, + CatchpointGenesisDir: coldPath, + HotGenesisDir: hotPath, }, } ct.initialize(conf, paths) ao.initialize(conf) - _, err := trackerDBInitialize(l, ct.catchpointEnabled(), dbPathPrefix) + _, err := trackerDBInitialize(l, ct.catchpointEnabled(), hotPath) require.NoError(tb, err) err = l.trackers.initialize(l, []ledgerTracker{au, ct, ao, &txTail{}}, conf) @@ -1180,109 +1184,139 @@ func TestCalculateCatchpointRounds(t *testing.T) { } } -// Test that pruning first stage catchpoint database records and catchpoint data files -// works. +// TestCatchpointFirstStageInfoPruning checks pruning first stage catchpoint database records and catchpoint data files. +// The test makes a catchpoint tracker and adds blocks into a mock ledger +// until it reaches expected number of catchpoint. Then checks if database records match to data files existence. +// Additional effect is that there are much more data files written during the process than catchpoints at the very end of the test +// because of automatic pruning so check that most data files are removed confirms pruning works. func TestCatchpointFirstStageInfoPruning(t *testing.T) { partitiontest.PartitionTest(t) - // create new protocol version, which has lower lookback - testProtocolVersion := - protocol.ConsensusVersion("test-protocol-TestFirstStageInfoPruning") - protoParams := config.Consensus[protocol.ConsensusCurrentVersion] - protoParams.CatchpointLookback = 32 - protoParams.EnableCatchpointsWithSPContexts = true - config.Consensus[testProtocolVersion] = protoParams - defer func() { - delete(config.Consensus, testProtocolVersion) - }() - - accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} - - ml := makeMockLedgerForTracker(t, false, 1, testProtocolVersion, accts) - defer ml.Close() - - cfg := config.GetDefaultLocal() - cfg.CatchpointInterval = 4 - cfg.CatchpointTracking = 2 - ct := newCatchpointTracker(t, ml, cfg, ".") - defer ct.close() - - temporaryDirectory := t.TempDir() - catchpointsDirectory := filepath.Join(temporaryDirectory, trackerdb.CatchpointDirName) - err := os.Mkdir(catchpointsDirectory, 0777) - require.NoError(t, err) - - ct.dbDirectory = temporaryDirectory - ct.tmpDir = temporaryDirectory + tempDir := t.TempDir() + var tests = []struct { + hotPath string + coldPath string + }{ + {"", ""}, + {"hot", "cold"}, + } - expectedNumEntries := protoParams.CatchpointLookback / cfg.CatchpointInterval + for _, test := range tests { + var testName string + if test.hotPath == test.coldPath { + testName = "dirs=same" + } else { + testName = "dirs=different" + } + t.Run(testName, func(t *testing.T) { + test.hotPath = filepath.Join(tempDir, test.hotPath) + test.coldPath = filepath.Join(tempDir, test.coldPath) + for _, path := range []string{test.hotPath, test.coldPath} { + _, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + err := os.MkdirAll(path, 0777) + require.NoError(t, err) + } + } - isCatchpointRound := func(rnd basics.Round) bool { - return (uint64(rnd) >= cfg.MaxAcctLookback) && - (uint64(rnd)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && - ((uint64(rnd)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) - } - isDataFileRound := func(rnd basics.Round) bool { - return ((uint64(rnd)-cfg.MaxAcctLookback+protoParams.CatchpointLookback)%cfg.CatchpointInterval == 0) - } + dataFileDirectory := filepath.Join(test.hotPath, trackerdb.CatchpointDirName) + err := os.Mkdir(dataFileDirectory, 0777) + require.NoError(t, err) - numCatchpointsCreated := uint64(0) - i := basics.Round(0) - lastCatchpointLabel := "" + // create new protocol version, which has lower lookback + testProtocolVersion := + protocol.ConsensusVersion("test-protocol-TestFirstStageInfoPruning") + protoParams := config.Consensus[protocol.ConsensusCurrentVersion] + protoParams.CatchpointLookback = 32 + protoParams.EnableCatchpointsWithSPContexts = true + config.Consensus[testProtocolVersion] = protoParams + defer func() { + delete(config.Consensus, testProtocolVersion) + }() + + accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} + + ml := makeMockLedgerForTracker(t, false, 1, testProtocolVersion, accts) + defer ml.Close() + + cfg := config.GetDefaultLocal() + cfg.CatchpointInterval = 4 + cfg.CatchpointTracking = 2 + ct := newCatchpointTrackerWithPaths(t, ml, cfg, test.hotPath, test.coldPath) + defer ct.close() + + expectedNumEntries := protoParams.CatchpointLookback / cfg.CatchpointInterval + + isCatchpointRound := func(rnd basics.Round) bool { + return (uint64(rnd) >= cfg.MaxAcctLookback) && + (uint64(rnd)-cfg.MaxAcctLookback > protoParams.CatchpointLookback) && + ((uint64(rnd)-cfg.MaxAcctLookback)%cfg.CatchpointInterval == 0) + } + isDataFileRound := func(rnd basics.Round) bool { + return ((uint64(rnd)-cfg.MaxAcctLookback+protoParams.CatchpointLookback)%cfg.CatchpointInterval == 0) + } - for numCatchpointsCreated < expectedNumEntries { - i++ + numCatchpointsCreated := uint64(0) + numDataFilesWritten := uint64(0) + i := basics.Round(0) + lastCatchpointLabel := "" - blk := bookkeeping.Block{ - BlockHeader: bookkeeping.BlockHeader{ - Round: basics.Round(i), - UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: testProtocolVersion, - }, - }, - } - delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) + for numCatchpointsCreated < expectedNumEntries { + i++ - ml.addBlock(blockEntry{block: blk}, delta) + blk := bookkeeping.Block{ + BlockHeader: bookkeeping.BlockHeader{ + Round: basics.Round(i), + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: testProtocolVersion, + }, + }, + } + delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, 0, 0) + + ml.addBlock(blockEntry{block: blk}, delta) + + if isDataFileRound(i) || isCatchpointRound(i) { + ml.trackers.committedUpTo(i) + ml.trackers.waitAccountsWriting() + // Let catchpoint data generation finish so that nothing gets skipped. + for ct.isWritingCatchpointDataFile() { + time.Sleep(time.Millisecond) + } + numDataFilesWritten++ + } - if isDataFileRound(i) || isCatchpointRound(i) { - ml.trackers.committedUpTo(i) - ml.trackers.waitAccountsWriting() - // Let catchpoint data generation finish so that nothing gets skipped. - for ct.isWritingCatchpointDataFile() { - time.Sleep(time.Millisecond) + if isCatchpointRound(i) { + catchpointLabel := ct.GetLastCatchpointLabel() + require.NotEqual(t, lastCatchpointLabel, catchpointLabel) + lastCatchpointLabel = catchpointLabel + numCatchpointsCreated++ + } } - } - if isCatchpointRound(i) { - catchpointLabel := ct.GetLastCatchpointLabel() - require.NotEqual(t, lastCatchpointLabel, catchpointLabel) - lastCatchpointLabel = catchpointLabel - numCatchpointsCreated++ - } - } + numEntries := uint64(0) + i -= basics.Round(cfg.MaxAcctLookback) + for i > 0 { + _, recordExists, err := ct.catchpointStore.SelectCatchpointFirstStageInfo(context.Background(), i) + require.NoError(t, err) - numEntries := uint64(0) - i -= basics.Round(cfg.MaxAcctLookback) - for i > 0 { - _, recordExists, err := ct.catchpointStore.SelectCatchpointFirstStageInfo(context.Background(), i) - require.NoError(t, err) + catchpointDataFilePath := filepath.Join(dataFileDirectory, makeCatchpointDataFilePath(i)) + _, err = os.Stat(catchpointDataFilePath) + if errors.Is(err, os.ErrNotExist) { + require.False(t, recordExists, i) + } else { + require.NoError(t, err) + require.True(t, recordExists, i) + numEntries++ + } - catchpointDataFilePath := - filepath.Join(catchpointsDirectory, makeCatchpointDataFilePath(i)) - _, err = os.Stat(catchpointDataFilePath) - if errors.Is(err, os.ErrNotExist) { - require.False(t, recordExists, i) - } else { - require.NoError(t, err) - require.True(t, recordExists, i) - numEntries++ - } + i-- + } - i-- + require.Equal(t, expectedNumEntries, numEntries) + require.Greater(t, numDataFilesWritten, numEntries) + }) } - - require.Equal(t, expectedNumEntries, numEntries) } // Test that on startup the catchpoint tracker restarts catchpoint's first stage if From 2bf8630bb32ac3d35f452257e6c259966d94b5c6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 21 Mar 2024 14:18:02 -0400 Subject: [PATCH 096/218] tests: Thread testing.T through so we panic less (#5938) --- agreement/service_test.go | 432 +++++++++++++++----------------------- 1 file changed, 175 insertions(+), 257 deletions(-) diff --git a/agreement/service_test.go b/agreement/service_test.go index 5cc05901ed..3426b4df3b 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -650,14 +650,10 @@ func createTestAccountsAndBalances(t *testing.T, numNodes int, rootSeed []byte) // add new account rootAddress to db { rootAccess, err := db.MakeAccessor(t.Name()+"root"+strconv.Itoa(i+off), false, true) - if err != nil { - panic(err) - } + require.NoError(t, err) seed = sha256.Sum256(seed[:]) // rehash every node to get different root addresses root, err := account.ImportRoot(rootAccess, seed) - if err != nil { - panic(err) - } + require.NoError(t, err) rootAddress = root.Address() } @@ -742,9 +738,7 @@ func setupAgreementWithValidator(t *testing.T, numNodes int, traceLevel traceLev for i := 0; i < numNodes; i++ { accessor, err := db.MakeAccessor(t.Name()+"_"+strconv.Itoa(i)+"_crash.db", false, true) - if err != nil { - panic(err) - } + require.NoError(t, err) dbAccessors[i] = accessor m := baseNetwork.monitors[nodeID(i)] @@ -826,23 +820,17 @@ func (m *coserviceMonitor) clearClock() { } } -func expectNewPeriod(clocks []timers.Clock[TimeoutType], zeroes uint) (newzeroes uint) { +func expectNewPeriod(t *testing.T, clocks []timers.Clock[TimeoutType], zeroes uint) (newzeroes uint) { zeroes++ for i := range clocks { - if clocks[i].(*testingClock).zeroes != zeroes { - errstr := fmt.Sprintf("unexpected number of zeroes: %v != %v", clocks[i].(*testingClock).zeroes, zeroes) - panic(errstr) - } + require.Equal(t, zeroes, clocks[i].(*testingClock).zeroes, "unexpected number of zeroes") } return zeroes } -func expectNoNewPeriod(clocks []timers.Clock[TimeoutType], zeroes uint) (newzeroes uint) { +func expectNoNewPeriod(t *testing.T, clocks []timers.Clock[TimeoutType], zeroes uint) (newzeroes uint) { for i := range clocks { - if clocks[i].(*testingClock).zeroes != zeroes { - errstr := fmt.Sprintf("unexpected number of zeroes: %v != %v", clocks[i].(*testingClock).zeroes, zeroes) - panic(errstr) - } + require.Equal(t, zeroes, clocks[i].(*testingClock).zeroes, "unexpected number of zeroes") } return zeroes } @@ -869,13 +857,13 @@ func triggerGlobalTimeoutType(timeoutType TimeoutType, clocks []timers.Clock[Tim activityMonitor.waitForQuiet() } -func runRound(clocks []timers.Clock[TimeoutType], activityMonitor *activityMonitor, zeroes uint, filterTimeout time.Duration) (newzeroes uint) { +func runRound(t *testing.T, clocks []timers.Clock[TimeoutType], activityMonitor *activityMonitor, zeroes uint, filterTimeout time.Duration) (newzeroes uint) { triggerGlobalTimeout(filterTimeout, TimeoutFilter, clocks, activityMonitor) - return expectNewPeriod(clocks, zeroes) + return expectNewPeriod(t, clocks, zeroes) } -func runRoundTriggerFilter(clocks []timers.Clock[TimeoutType], activityMonitor *activityMonitor, zeroes uint) (newzeroes uint) { +func runRoundTriggerFilter(t *testing.T, clocks []timers.Clock[TimeoutType], activityMonitor *activityMonitor, zeroes uint) (newzeroes uint) { triggerGlobalTimeoutType(TimeoutFilter, clocks, activityMonitor) - return expectNewPeriod(clocks, zeroes) + return expectNewPeriod(t, clocks, zeroes) } func sanityCheck(startRound round, numRounds round, ledgers []Ledger) { @@ -916,19 +904,19 @@ func simulateAgreementWithLedgerFactory(t *testing.T, numNodes int, numRounds in } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) filterTimeouts := make([][]time.Duration, numNodes, numNodes) // run round with round-specific consensus version first (since fix in #1896) - zeroes = runRoundTriggerFilter(clocks, activityMonitor, zeroes) + zeroes = runRoundTriggerFilter(t, clocks, activityMonitor, zeroes) for j := 1; j < numRounds; j++ { for srvIdx, clock := range clocks { delta, err := clock.(*testingClock).when(TimeoutFilter) require.NoError(t, err) filterTimeouts[srvIdx] = append(filterTimeouts[srvIdx], delta) } - zeroes = runRoundTriggerFilter(clocks, activityMonitor, zeroes) + zeroes = runRoundTriggerFilter(t, clocks, activityMonitor, zeroes) } for i := 0; i < numNodes; i++ { @@ -1127,21 +1115,21 @@ func TestDynamicFilterTimeoutResets(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) filterTimeouts := make([][]time.Duration, numNodes, numNodes) baseHistoryRounds := dynamicFilterCredentialArrivalHistory + int(credentialRoundLag) // run round with round-specific consensus version first (since fix in #1896) - zeroes = runRoundTriggerFilter(clocks, activityMonitor, zeroes) + zeroes = runRoundTriggerFilter(t, clocks, activityMonitor, zeroes) for j := 1; j < baseHistoryRounds+2; j++ { for srvIdx, clock := range clocks { delta, err := clock.(*testingClock).when(TimeoutFilter) require.NoError(t, err) filterTimeouts[srvIdx] = append(filterTimeouts[srvIdx], delta) } - zeroes = runRoundTriggerFilter(clocks, activityMonitor, zeroes) + zeroes = runRoundTriggerFilter(t, clocks, activityMonitor, zeroes) } for i := range clocks { @@ -1158,36 +1146,36 @@ func TestDynamicFilterTimeoutResets(t *testing.T) { baseNetwork.dropAllSlowNextVotes() triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeoutType(TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(firstFPR, TimeoutFastRecovery, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // terminate on period 1 { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } filterTimeoutsPostRecovery := make([][]time.Duration, numNodes, numNodes) // run round with round-specific consensus version first (since fix in #1896) - zeroes = runRoundTriggerFilter(clocks, activityMonitor, zeroes) + zeroes = runRoundTriggerFilter(t, clocks, activityMonitor, zeroes) for j := 1; j < baseHistoryRounds+1; j++ { for srvIdx, clock := range clocks { delta, err := clock.(*testingClock).when(TimeoutFilter) require.NoError(t, err) filterTimeoutsPostRecovery[srvIdx] = append(filterTimeoutsPostRecovery[srvIdx], delta) } - zeroes = runRoundTriggerFilter(clocks, activityMonitor, zeroes) + zeroes = runRoundTriggerFilter(t, clocks, activityMonitor, zeroes) } for i := range clocks { @@ -1262,11 +1250,11 @@ func TestAgreementFastRecoveryDownEarly(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // force fast partition recovery into bottom @@ -1275,28 +1263,28 @@ func TestAgreementFastRecoveryDownEarly(t *testing.T) { baseNetwork.dropAllSlowNextVotes() triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeoutType(TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(firstFPR, TimeoutFastRecovery, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // terminate on period 1 { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { @@ -1320,11 +1308,11 @@ func TestAgreementFastRecoveryDownMiss(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // force fast partition recovery into bottom @@ -1332,13 +1320,13 @@ func TestAgreementFastRecoveryDownMiss(t *testing.T) { // fail all steps baseNetwork.dropAllVotes() triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) firstClocks := clocks[:4] restClocks := clocks[4:] @@ -1351,7 +1339,7 @@ func TestAgreementFastRecoveryDownMiss(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.repairAll() for i := range restClocks { @@ -1362,22 +1350,22 @@ func TestAgreementFastRecoveryDownMiss(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(secondFPR, TimeoutFastRecovery, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // terminate on period 1 { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { @@ -1401,11 +1389,11 @@ func TestAgreementFastRecoveryLate(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // force fast partition recovery into value @@ -1415,31 +1403,26 @@ func TestAgreementFastRecoveryLate(t *testing.T) { closeFn := baseNetwork.pocketAllCertVotes(pocket) baseNetwork.dropAllSlowNextVotes() triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() for msg := range pocket { var uv unauthenticatedVote err := protocol.DecodeStream(bytes.NewBuffer(msg.data), &uv) - if err != nil { - panic(err) - } + require.NoError(t, err) if expected == (proposalValue{}) { expected = uv.R.Proposal } else { - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected proposal: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.Equal(t, expected, uv.R.Proposal, "unexpected proposal") } } triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.dropAllVotes() firstClocks := clocks[:4] @@ -1453,7 +1436,7 @@ func TestAgreementFastRecoveryLate(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.repairAll() for i := range restClocks { @@ -1464,33 +1447,28 @@ func TestAgreementFastRecoveryLate(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(secondFPR, TimeoutFastRecovery, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // terminate on period 1 { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } for _, l := range ledgers { lastHash, err := l.LookupDigest(l.NextRound() - 1) - if err != nil { - panic(err) - } - if lastHash != expected.BlockDigest { - errstr := fmt.Sprintf("converged on wrong block: %v != %v", lastHash, expected.BlockDigest) - panic(errstr) - } + require.NoError(t, err) + require.Equal(t, expected.BlockDigest, lastHash, "converged on wrong block") } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { @@ -1514,11 +1492,11 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // force fast partition recovery into value @@ -1528,31 +1506,26 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { closeFn := baseNetwork.pocketAllCertVotes(pocket) baseNetwork.dropAllSlowNextVotes() triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() for msg := range pocket { var uv unauthenticatedVote err := protocol.DecodeStream(bytes.NewBuffer(msg.data), &uv) - if err != nil { - panic(err) - } + require.NoError(t, err) if expected == (proposalValue{}) { expected = uv.R.Proposal } else { - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected proposal: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.Equal(t, expected, uv.R.Proposal, "unexpected proposal") } } triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.dropAllVotes() firstClocks := clocks[:4] @@ -1566,7 +1539,7 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.repairAll() for i := range restClocks { @@ -1577,23 +1550,23 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(secondFPR, TimeoutFastRecovery, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // fail period 1 with value again { baseNetwork.dropAllVotes() triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(0, TimeoutFastRecovery, clocks, activityMonitor) // activates fast partition recovery timer - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.dropAllVotes() firstClocks := clocks[:4] @@ -1607,7 +1580,7 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.repairAll() for i := range restClocks { @@ -1618,33 +1591,28 @@ func TestAgreementFastRecoveryRedo(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(secondFPR, TimeoutFastRecovery, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // terminate on period 2 { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(2, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } for _, l := range ledgers { lastHash, err := l.LookupDigest(l.NextRound() - 1) - if err != nil { - panic(err) - } - if lastHash != expected.BlockDigest { - errstr := fmt.Sprintf("converged on wrong block: %v != %v", lastHash, expected.BlockDigest) - panic(errstr) - } + require.NoError(t, err) + require.Equal(t, expected.BlockDigest, lastHash, "converged on wrong block") } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { @@ -1668,42 +1636,42 @@ func TestAgreementBlockReplayBug_b29ea57(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // fail period 0 { baseNetwork.dropAllSoftVotes() triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // fail period 1 on bottom with block { triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // terminate on period 2 { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(2, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { @@ -1727,11 +1695,11 @@ func TestAgreementLateCertBug(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // delay minority cert votes to force period 1 @@ -1739,12 +1707,12 @@ func TestAgreementLateCertBug(t *testing.T) { { closeFn := baseNetwork.pocketAllCertVotes(pocket) triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() baseNetwork.repairAll() triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // terminate on period 0 in period 1 @@ -1756,12 +1724,12 @@ func TestAgreementLateCertBug(t *testing.T) { baseNetwork.finishAllMulticast() activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { @@ -1785,11 +1753,11 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // force partition recovery into value @@ -1799,28 +1767,23 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) { closeFn := baseNetwork.pocketAllCertVotes(pocket) triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() for msg := range pocket { var uv unauthenticatedVote err := protocol.DecodeStream(bytes.NewBuffer(msg.data), &uv) - if err != nil { - panic(err) - } + require.NoError(t, err) if expected == (proposalValue{}) { expected = uv.R.Proposal } else { - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected proposal: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.Equal(t, expected, uv.R.Proposal, "unexpected proposal") } } triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 4, int(zeroes)) } @@ -1830,24 +1793,18 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) { closeFn := baseNetwork.pocketAllCertVotes(pocket) triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() for msg := range pocket { var uv unauthenticatedVote err := protocol.DecodeStream(bytes.NewBuffer(msg.data), &uv) - if err != nil { - panic(err) - } - - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected proposal: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.NoError(t, err) + require.Equal(t, expected, uv.R.Proposal, "unexpected proposal") } triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 5, int(zeroes)) } @@ -1856,13 +1813,13 @@ func TestAgreementRecoverGlobalStartingValue(t *testing.T) { { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(2, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 6, int(zeroes)) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { services[i].Shutdown() @@ -1885,11 +1842,11 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // force partition recovery into value. @@ -1898,23 +1855,18 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) { pocket := make(chan multicastParams, 100) closeFn := baseNetwork.pocketAllCertVotes(pocket) triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() for msg := range pocket { var uv unauthenticatedVote err := protocol.DecodeStream(bytes.NewBuffer(msg.data), &uv) - if err != nil { - panic(err) - } + require.NoError(t, err) if expected == (proposalValue{}) { expected = uv.R.Proposal } else { - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected proposal: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.Equal(t, expected, uv.R.Proposal, "unexpected proposal") } } // intercept all proposals for the next period; replace with unexpected @@ -1925,7 +1877,7 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) { return params }) triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 4, int(zeroes)) } @@ -1935,23 +1887,17 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) { pocket := make(chan multicastParams, 100) closeFn := baseNetwork.pocketAllCertVotes(pocket) triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() for msg := range pocket { var uv unauthenticatedVote err := protocol.DecodeStream(bytes.NewBuffer(msg.data), &uv) - if err != nil { - panic(err) - } - - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected proposal: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.NoError(t, err) + require.Equal(t, expected, uv.R.Proposal, "unexpected proposal") } triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } @@ -1959,13 +1905,13 @@ func TestAgreementRecoverGlobalStartingValueBadProposal(t *testing.T) { { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(2, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 6, int(zeroes)) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { services[i].Shutdown() @@ -1988,11 +1934,11 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // force partition recovery into both bottom and value. one node enters bottom, the rest enter value @@ -2001,7 +1947,7 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { pocket := make(chan multicastParams, 100) closeFn := baseNetwork.pocketAllSoftVotes(pocket) triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() pocketedSoft := make([]multicastParams, len(pocket)) i := 0 @@ -2009,16 +1955,11 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { r := bytes.NewBuffer(params.data) var uv unauthenticatedVote err := protocol.DecodeStream(r, &uv) - if err != nil { - panic(err) - } + require.NoError(t, err) if expected == (proposalValue{}) { expected = uv.R.Proposal } else { - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected soft vote: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.Equal(t, expected, uv.R.Proposal, "unexpected soft vote") } pocketedSoft[i] = params i++ @@ -2026,11 +1967,9 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { // generate a bottom quorum; let only one node see it. baseNetwork.crown(0) triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - if clocks[0].(*testingClock).zeroes != zeroes+1 { - errstr := fmt.Sprintf("node 0 did not enter new period from bot quorum") - panic(errstr) - } - zeroes = expectNoNewPeriod(clocks[1:], zeroes) + require.Equal(t, zeroes+1, clocks[0].(*testingClock).zeroes, + "node 0 did not enter new period from bot quorum") + zeroes = expectNoNewPeriod(t, clocks[1:], zeroes) // enable creation of a value quorum; let everyone else see it baseNetwork.repairAll() @@ -2045,12 +1984,12 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { // actually create the value quorum _, upper := (next).nextVoteRanges(DeadlineTimeout(0, version)) triggerGlobalTimeout(upper, TimeoutDeadline, clocks[1:], activityMonitor) // activates next timers - zeroes = expectNoNewPeriod(clocks[1:], zeroes) + zeroes = expectNoNewPeriod(t, clocks[1:], zeroes) lower, upper := (next + 1).nextVoteRanges(DeadlineTimeout(0, version)) delta := time.Duration(testingRand{}.Uint64() % uint64(upper-lower)) triggerGlobalTimeout(lower+delta, TimeoutDeadline, clocks[1:], activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 4, int(zeroes)) } @@ -2060,37 +1999,31 @@ func TestAgreementRecoverBothVAndBotQuorums(t *testing.T) { pocket := make(chan multicastParams, 100) closeFn := baseNetwork.pocketAllCertVotes(pocket) triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) closeFn() for msg := range pocket { var uv unauthenticatedVote err := protocol.DecodeStream(bytes.NewBuffer(msg.data), &uv) - if err != nil { - panic(err) - } - - if uv.R.Proposal != expected { - errstr := fmt.Sprintf("got unexpected proposal: %v != %v", uv.R.Proposal, expected) - panic(errstr) - } + require.NoError(t, err) + require.Equal(t, expected, uv.R.Proposal, "got unexpected proposal") } triggerGlobalTimeout(DeadlineTimeout(1, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // Finish in period 2 { baseNetwork.repairAll() triggerGlobalTimeout(FilterTimeout(2, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 6, int(zeroes)) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { services[i].Shutdown() @@ -2113,11 +2046,11 @@ func TestAgreementSlowPayloadsPreDeadline(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // run round and then start pocketing payloads @@ -2125,13 +2058,13 @@ func TestAgreementSlowPayloadsPreDeadline(t *testing.T) { closeFn := baseNetwork.pocketAllCompound(pocket) // (takes effect next round) { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run round with late payload { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) // release payloads; expect new round closeFn() @@ -2143,12 +2076,12 @@ func TestAgreementSlowPayloadsPreDeadline(t *testing.T) { baseNetwork.finishAllMulticast() activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { services[i].Shutdown() @@ -2171,11 +2104,11 @@ func TestAgreementSlowPayloadsPostDeadline(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // run round and then start pocketing payloads @@ -2183,15 +2116,15 @@ func TestAgreementSlowPayloadsPostDeadline(t *testing.T) { closeFn := baseNetwork.pocketAllCompound(pocket) // (takes effect next round) { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // force network into period 1 by delaying proposals { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(DeadlineTimeout(0, version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // recover in period 1 @@ -2205,15 +2138,15 @@ func TestAgreementSlowPayloadsPostDeadline(t *testing.T) { baseNetwork.finishAllMulticast() activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) triggerGlobalTimeout(FilterTimeout(1, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { services[i].Shutdown() @@ -2236,11 +2169,11 @@ func TestAgreementLargePeriods(t *testing.T) { activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // partition the network, run until period 60 @@ -2248,11 +2181,11 @@ func TestAgreementLargePeriods(t *testing.T) { { baseNetwork.partition(0, 1, 2) triggerGlobalTimeout(FilterTimeout(period(p), version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) baseNetwork.repairAll() triggerGlobalTimeout(DeadlineTimeout(period(p), version), TimeoutDeadline, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) require.Equal(t, 4+p, int(zeroes)) } } @@ -2260,12 +2193,12 @@ func TestAgreementLargePeriods(t *testing.T) { // terminate { triggerGlobalTimeout(FilterTimeout(60, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // run two more rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } for i := 0; i < numNodes; i++ { services[i].Shutdown() @@ -2273,9 +2206,8 @@ func TestAgreementLargePeriods(t *testing.T) { const expectNumRounds = 5 for i := 0; i < numNodes; i++ { - if ledgers[i].NextRound() != startRound+round(expectNumRounds) { - panic("did not progress 5 rounds") - } + require.Equal(t, startRound+round(expectNumRounds), ledgers[i].NextRound(), + "did not progress 5 rounds") } for j := 0; j < expectNumRounds; j++ { @@ -2283,9 +2215,7 @@ func TestAgreementLargePeriods(t *testing.T) { reference := ledger.entries[startRound+round(j)].Digest() for i := 0; i < numNodes; i++ { ledger := ledgers[i].(*testLedger) - if ledger.entries[startRound+round(j)].Digest() != reference { - panic("wrong block confirmed") - } + require.Equal(t, reference, ledger.entries[startRound+round(j)].Digest(), "wrong block confirmed") } } } @@ -2334,11 +2264,11 @@ func TestAgreementRegression_WrongPeriodPayloadVerificationCancellation_8ba23942 } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds for j := 0; j < 2; j++ { - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) } // run round and then start pocketing payloads, suspending validation @@ -2347,13 +2277,13 @@ func TestAgreementRegression_WrongPeriodPayloadVerificationCancellation_8ba23942 closeFn := baseNetwork.pocketAllCompound(pocket0) // (takes effect next round) { triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) } // force network into period 1 by failing period 0, entering with bottom and no soft threshold (to prevent proposal value pinning) baseNetwork.dropAllSoftVotes() triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNoNewPeriod(clocks, zeroes) + zeroes = expectNoNewPeriod(t, clocks, zeroes) // resume delivery of payloads in following period baseNetwork.repairAll() @@ -2440,15 +2370,15 @@ func TestAgreementRegression_WrongPeriodPayloadVerificationCancellation_8ba23942 } baseNetwork.finishAllMulticast() - zeroes = expectNewPeriod(clocks, zeroes) + zeroes = expectNewPeriod(t, clocks, zeroes) activityMonitor.waitForQuiet() // run two more rounds //for j := 0; j < 2; j++ { - // zeroes = runRound(clocks, activityMonitor, zeroes, period(1-j)) + // zeroes = runRound(t, clocks, activityMonitor, zeroes, period(1-j)) //} - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(1, version)) - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(1, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) for i := 0; i < numNodes; i++ { services[i].Shutdown() @@ -2456,9 +2386,8 @@ func TestAgreementRegression_WrongPeriodPayloadVerificationCancellation_8ba23942 const expectNumRounds = 5 for i := 0; i < numNodes; i++ { - if ledgers[i].NextRound() != startRound+round(expectNumRounds) { - panic("did not progress 5 rounds") - } + require.Equal(t, startRound+round(expectNumRounds), ledgers[i].NextRound(), + "did not progress 5 rounds") } for j := 0; j < expectNumRounds; j++ { @@ -2466,9 +2395,7 @@ func TestAgreementRegression_WrongPeriodPayloadVerificationCancellation_8ba23942 reference := ledger.entries[startRound+round(j)].Digest() for i := 0; i < numNodes; i++ { ledger := ledgers[i].(*testLedger) - if ledger.entries[startRound+round(j)].Digest() != reference { - panic("wrong block confirmed") - } + require.Equal(t, reference, ledger.entries[startRound+round(j)].Digest(), "wrong block confirmed") } } } @@ -2491,17 +2418,15 @@ func TestAgreementCertificateDoesNotStallSingleRelay(t *testing.T) { } activityMonitor.waitForActivity() activityMonitor.waitForQuiet() - zeroes := expectNewPeriod(clocks, 0) + zeroes := expectNewPeriod(t, clocks, 0) // run two rounds - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) // make sure relay does not see block proposal for round 3 baseNetwork.intercept(func(params multicastParams) multicastParams { if params.tag == protocol.ProposalPayloadTag { var tp transmittedPayload err := protocol.DecodeStream(bytes.NewBuffer(params.data), &tp) - if err != nil { - panic(err) - } + require.NoError(t, err) if tp.Round() == basics.Round(startRound+2) { params.exclude = relayID } @@ -2512,9 +2437,7 @@ func TestAgreementCertificateDoesNotStallSingleRelay(t *testing.T) { if params.tag == protocol.AgreementVoteTag { var uv unauthenticatedVote err := protocol.DecodeStream(r, &uv) - if err != nil { - panic(err) - } + require.NoError(t, err) if uv.R.Step != propose { return params } @@ -2524,7 +2447,7 @@ func TestAgreementCertificateDoesNotStallSingleRelay(t *testing.T) { return params }) - zeroes = runRound(clocks, activityMonitor, zeroes, FilterTimeout(0, version)) + zeroes = runRound(t, clocks, activityMonitor, zeroes, FilterTimeout(0, version)) // Round 3: // First partition the relay to prevent it from seeing certificate or block @@ -2537,9 +2460,7 @@ func TestAgreementCertificateDoesNotStallSingleRelay(t *testing.T) { r := bytes.NewBuffer(params.data) var uv unauthenticatedVote err := protocol.DecodeStream(r, &uv) - if err != nil { - panic(err) - } + require.NoError(t, err) if uv.R.Step == cert { pocketCert <- params } @@ -2548,7 +2469,7 @@ func TestAgreementCertificateDoesNotStallSingleRelay(t *testing.T) { }) // And with some hypothetical second relay the network achieves consensus on a certificate and block. triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks, activityMonitor) - zeroes = expectNewPeriod(clocks[1:], zeroes) + zeroes = expectNewPeriod(t, clocks[1:], zeroes) require.Equal(t, uint(3), clocks[0].(*testingClock).zeroes) close(pocketCert) @@ -2567,7 +2488,7 @@ func TestAgreementCertificateDoesNotStallSingleRelay(t *testing.T) { // this relay must still relay initial messages. Note that payloads were already relayed with // the previous global timeout. triggerGlobalTimeout(FilterTimeout(0, version), TimeoutFilter, clocks[1:], activityMonitor) - zeroes = expectNewPeriod(clocks[1:], zeroes) + zeroes = expectNewPeriod(t, clocks[1:], zeroes) require.Equal(t, uint(3), clocks[0].(*testingClock).zeroes) for i := 0; i < numNodes; i++ { @@ -2575,18 +2496,15 @@ func TestAgreementCertificateDoesNotStallSingleRelay(t *testing.T) { } const expectNumRounds = 4 for i := 1; i < numNodes; i++ { - if ledgers[i].NextRound() != startRound+round(expectNumRounds) { - panic("did not progress 4 rounds") - } + require.Equal(t, startRound+round(expectNumRounds), ledgers[i].NextRound(), + "did not progress 4 rounds") } for j := 0; j < expectNumRounds; j++ { ledger := ledgers[1].(*testLedger) reference := ledger.entries[startRound+round(j)].Digest() for i := 1; i < numNodes; i++ { ledger := ledgers[i].(*testLedger) - if ledger.entries[startRound+round(j)].Digest() != reference { - panic("wrong block confirmed") - } + require.Equal(t, reference, ledger.entries[startRound+round(j)].Digest(), "wrong block confirmed") } } } From d0bf8c6ecf210a8459f4e1fed6c8c7d9bd334e9c Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:18:39 -0400 Subject: [PATCH 097/218] tests: dump logs at the end of exit callback (#5952) --- test/framework/fixtures/libgoalFixture.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 474c902186..bb96058136 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -120,8 +120,10 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e return } - f.t.Logf("Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err) - f.dumpLogs(filepath.Join(nc.GetDataDir(), "node.log")) + defer func() { + f.t.Logf("Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err) + f.dumpLogs(filepath.Join(nc.GetDataDir(), "node.log")) + }() exitError, ok := err.(*exec.ExitError) if !ok { From 4a961681d305fc6d32de24798362a364bd0a2b1b Mon Sep 17 00:00:00 2001 From: Henrik Soerensen Date: Thu, 21 Mar 2024 12:19:14 -0600 Subject: [PATCH 098/218] docker: Remove usage of `gosu` and replace with `su` (#5951) --- Dockerfile | 2 +- docker/files/run/run.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 48bc652dcd..189b9501f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,7 +46,7 @@ FROM debian:bookworm-20240311-slim as final ENV PATH="/node/bin:${PATH}" ALGOD_PORT="8080" KMD_PORT="7833" ALGORAND_DATA="/algod/data" # curl is needed to lookup the fast catchup url -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl gosu && \ +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && \ update-ca-certificates && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ diff --git a/docker/files/run/run.sh b/docker/files/run/run.sh index 29b69423dc..d92e7e1d1f 100755 --- a/docker/files/run/run.sh +++ b/docker/files/run/run.sh @@ -17,7 +17,7 @@ fi # as the algorand user. if [ "$(id -u)" = '0' ]; then chown -R algorand:algorand $ALGORAND_DATA - exec gosu algorand "$0" "$@" + exec su -p -c "$(readlink -f $0) $@" algorand fi # Script to configure or resume a network. Based on environment settings the From 62233ea2cf67bff28c9fe0c0166367b4744af7b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:31:53 -0400 Subject: [PATCH 099/218] Build: bump google.golang.org/protobuf from 1.30.0 to 1.33.0 in /tools/block-generator (#5954) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/block-generator/go.mod | 2 +- tools/block-generator/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 965cbd9a09..3698b6ca8e 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -137,7 +137,7 @@ require ( 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 + google.golang.org/protobuf v1.33.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 dbba8cc727..c926d083a4 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -1007,8 +1007,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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= From c923d5d12cf92d4e92ee8dd980ee5b32715fcc8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:33:12 -0400 Subject: [PATCH 100/218] Build: bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#5877) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ohill <145173879+ohill@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- tools/block-generator/go.mod | 6 +++--- tools/block-generator/go.sum | 12 ++++++------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 0d85043883..403d02010b 100644 --- a/go.mod +++ b/go.mod @@ -41,11 +41,11 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.17.0 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 golang.org/x/sync v0.3.0 - golang.org/x/sys v0.13.0 - golang.org/x/text v0.13.0 + golang.org/x/sys v0.15.0 + golang.org/x/text v0.14.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 pgregory.net/rapid v0.6.2 ) @@ -161,7 +161,7 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect golang.org/x/tools v0.11.0 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index f8d0ac3a04..63ef5eb7b9 100644 --- a/go.sum +++ b/go.sum @@ -738,8 +738,8 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh 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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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= @@ -910,12 +910,12 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc 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.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/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -923,8 +923,8 @@ 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 3698b6ca8e..33333cd47b 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -129,13 +129,13 @@ require ( 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/crypto v0.17.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.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/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.11.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index c926d083a4..8d2ff27efb 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -675,8 +675,8 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh 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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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= @@ -844,8 +844,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc 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.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/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= @@ -855,8 +855,8 @@ 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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= From 93ab75854c1cf55fa4f80cb27d7542f457fde570 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:03:41 -0400 Subject: [PATCH 101/218] metrics: support labeled gauges (#5956) --- util/metrics/couge.go | 211 ++++++++++++++++++++++++++++++++++ util/metrics/counter.go | 154 +++---------------------- util/metrics/counterCommon.go | 42 ------- util/metrics/counter_test.go | 39 +++++++ util/metrics/gauge.go | 58 ++++------ util/metrics/gauge_test.go | 42 ++++++- 6 files changed, 331 insertions(+), 215 deletions(-) create mode 100644 util/metrics/couge.go delete mode 100644 util/metrics/counterCommon.go diff --git a/util/metrics/couge.go b/util/metrics/couge.go new file mode 100644 index 0000000000..503265efa9 --- /dev/null +++ b/util/metrics/couge.go @@ -0,0 +1,211 @@ +// Copyright (C) 2019-2024 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 . + +// Common code for COUnters and gaUGEs. + +package metrics + +import ( + "math" + "strconv" + "strings" + "sync/atomic" + + "github.com/algorand/go-deadlock" +) + +type couge struct { + // Collects value for special fast-path with no labels through Inc(nil) AddUint64(x, nil) + intValue atomic.Uint64 + + deadlock.Mutex + name string + description string + values []*cougeValues + labels map[string]int // map each label ( i.e. httpErrorCode ) to an index. + valuesIndices map[int]int +} + +type cougeValues struct { + value uint64 + labels map[string]string + formattedLabels string +} + +func (cv *cougeValues) createFormattedLabel() { + var buf strings.Builder + if len(cv.labels) < 1 { + return + } + for k, v := range cv.labels { + buf.WriteString("," + k + "=\"" + v + "\"") + } + + cv.formattedLabels = buf.String()[1:] +} + +func (cg *couge) findLabelIndex(labels map[string]string) int { + accumulatedIndex := 0 + for k, v := range labels { + t := k + ":" + v + // do we already have this key ( label ) in our map ? + if i, has := cg.labels[t]; has { + // yes, we do. use this index. + accumulatedIndex += i + } else { + // no, we don't have it. + cg.labels[t] = int(math.Exp2(float64(len(cg.labels)))) + accumulatedIndex += cg.labels[t] + } + } + return accumulatedIndex +} + +func (cg *couge) fastAddUint64(x uint64) { + if cg.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. + // Dummy counterValue simplifies display in WriteMetric. + cg.addLabels(0, nil) + } +} + +// addLabels increases counter by x +func (cg *couge) addLabels(x uint64, labels map[string]string) { + cg.Lock() + defer cg.Unlock() + + labelIndex := cg.findLabelIndex(labels) + + // find where we have the same labels. + if counterIdx, has := cg.valuesIndices[labelIndex]; !has { + // we need to add a new counter. + val := &cougeValues{ + value: x, + labels: labels, + } + val.createFormattedLabel() + cg.values = append(cg.values, val) + cg.valuesIndices[labelIndex] = len(cg.values) - 1 + } else { + // update existing value. + cg.values[counterIdx].value += x + } +} + +// setLabels sets value to x +func (cg *couge) setLabels(x uint64, labels map[string]string) { + cg.Lock() + defer cg.Unlock() + + labelIndex := cg.findLabelIndex(labels) + + // find where we have the same labels. + if counterIdx, has := cg.valuesIndices[labelIndex]; !has { + // we need to set a new value. + val := &cougeValues{ + value: x, + labels: labels, + } + val.createFormattedLabel() + cg.values = append(cg.values, val) + cg.valuesIndices[labelIndex] = len(cg.values) - 1 + } else { + // update existing value. + cg.values[counterIdx].value = x + } +} + +// getUint64ValueForLabels returns the value of the counter for the given labels or 0 if it's not found. +func (cg *couge) getUint64ValueForLabels(labels map[string]string) uint64 { + cg.Lock() + defer cg.Unlock() + + labelIndex := cg.findLabelIndex(labels) + counterIdx, has := cg.valuesIndices[labelIndex] + if !has { + return 0 + } + return cg.values[counterIdx].value +} + +// writeMetric writes the metric into the output stream +func (cg *couge) writeMetric(buf *strings.Builder, metricType string, parentLabels string) { + cg.Lock() + defer cg.Unlock() + + buf.WriteString("# HELP ") + buf.WriteString(cg.name) + buf.WriteString(" ") + buf.WriteString(cg.description) + buf.WriteString("\n# TYPE ") + buf.WriteString(cg.name) + buf.WriteString(" " + metricType + "\n") + // if counter is zero, report 0 using parentLabels and no tags + if len(cg.values) == 0 { + buf.WriteString(cg.name) + if len(parentLabels) > 0 { + buf.WriteString("{" + parentLabels + "}") + } + buf.WriteString(" " + strconv.FormatUint(cg.intValue.Load(), 10)) + buf.WriteString("\n") + return + } + // otherwise iterate through values and write one line per label + for _, l := range cg.values { + buf.WriteString(cg.name) + if len(parentLabels) > 0 || len(l.formattedLabels) > 0 { + buf.WriteString("{") + if len(parentLabels) > 0 { + buf.WriteString(parentLabels) + if len(l.formattedLabels) > 0 { + buf.WriteString(",") + } + } + buf.WriteString(l.formattedLabels) + buf.WriteString("}") + } + value := l.value + if len(l.labels) == 0 { + value += cg.intValue.Load() + } + buf.WriteString(" " + strconv.FormatUint(value, 10)) + buf.WriteString("\n") + } +} + +// addMetric adds the metric into the map +func (cg *couge) addMetric(values map[string]float64) { + cg.Lock() + defer cg.Unlock() + + if len(cg.values) < 1 { + return + } + + for _, l := range cg.values { + sum := l.value + if len(l.labels) == 0 { + sum += cg.intValue.Load() + } + var suffix string + if len(l.formattedLabels) > 0 { + suffix = ":" + l.formattedLabels + } + values[sanitizeTelemetryName(cg.name+suffix)] = float64(sum) + } +} diff --git a/util/metrics/counter.go b/util/metrics/counter.go index 59b1bb2747..38852386d8 100644 --- a/util/metrics/counter.go +++ b/util/metrics/counter.go @@ -17,21 +17,24 @@ package metrics import ( - "math" - "strconv" "strings" "time" ) +// Counter represent a single counter variable. +type Counter struct { + c couge +} + // MakeCounter create a new counter with the provided name and description. func MakeCounter(metric MetricName) *Counter { - c := &Counter{ - values: make([]*counterValues, 0), + c := &Counter{c: couge{ + values: make([]*cougeValues, 0), description: metric.Description, name: metric.Name, labels: make(map[string]int), valuesIndices: make(map[int]int), - } + }} c.Register(nil) return c } @@ -63,32 +66,9 @@ func (counter *Counter) Deregister(reg *Registry) { // Much faster if labels is nil or empty. func (counter *Counter) Inc(labels map[string]string) { if len(labels) == 0 { - counter.fastAddUint64(1) + counter.c.fastAddUint64(1) } else { - counter.addLabels(1.0, labels) - } -} - -// addLabels increases counter by x -func (counter *Counter) addLabels(x uint64, labels map[string]string) { - counter.Lock() - defer counter.Unlock() - - labelIndex := counter.findLabelIndex(labels) - - // find where we have the same labels. - if counterIdx, has := counter.valuesIndices[labelIndex]; !has { - // we need to add a new counter. - val := &counterValues{ - counter: x, - labels: labels, - } - val.createFormattedLabel() - counter.values = append(counter.values, val) - counter.valuesIndices[labelIndex] = len(counter.values) - 1 - } else { - // update existing value. - counter.values[counterIdx].counter += x + counter.c.addLabels(1.0, labels) } } @@ -96,9 +76,9 @@ func (counter *Counter) addLabels(x uint64, labels map[string]string) { // If labels is nil this is much faster than if labels is not nil. func (counter *Counter) AddUint64(x uint64, labels map[string]string) { if len(labels) == 0 { - counter.fastAddUint64(x) + counter.c.fastAddUint64(x) } else { - counter.addLabels(x, labels) + counter.c.addLabels(x, labels) } } @@ -110,122 +90,20 @@ 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 counter.intValue.Load() + return counter.c.intValue.Load() } // GetUint64ValueForLabels returns the value of the counter for the given labels or 0 if it's not found. func (counter *Counter) GetUint64ValueForLabels(labels map[string]string) uint64 { - counter.Lock() - defer counter.Unlock() - - labelIndex := counter.findLabelIndex(labels) - counterIdx, has := counter.valuesIndices[labelIndex] - if !has { - return 0 - } - return counter.values[counterIdx].counter -} - -func (counter *Counter) fastAddUint64(x uint64) { - 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. - // Dummy counterValue simplifies display in WriteMetric. - counter.addLabels(0, nil) - } -} - -func (counter *Counter) findLabelIndex(labels map[string]string) int { - accumulatedIndex := 0 - for k, v := range labels { - t := k + ":" + v - // do we already have this key ( label ) in our map ? - if i, has := counter.labels[t]; has { - // yes, we do. use this index. - accumulatedIndex += i - } else { - // no, we don't have it. - counter.labels[t] = int(math.Exp2(float64(len(counter.labels)))) - accumulatedIndex += counter.labels[t] - } - } - return accumulatedIndex -} - -func (cv *counterValues) createFormattedLabel() { - var buf strings.Builder - if len(cv.labels) < 1 { - return - } - for k, v := range cv.labels { - buf.WriteString("," + k + "=\"" + v + "\"") - } - - cv.formattedLabels = buf.String()[1:] + return counter.c.getUint64ValueForLabels(labels) } // WriteMetric writes the metric into the output stream func (counter *Counter) WriteMetric(buf *strings.Builder, parentLabels string) { - counter.Lock() - defer counter.Unlock() - - buf.WriteString("# HELP ") - buf.WriteString(counter.name) - buf.WriteString(" ") - buf.WriteString(counter.description) - buf.WriteString("\n# TYPE ") - buf.WriteString(counter.name) - buf.WriteString(" counter\n") - // if counter is zero, report 0 using parentLabels and no tags - if len(counter.values) == 0 { - buf.WriteString(counter.name) - if len(parentLabels) > 0 { - buf.WriteString("{" + parentLabels + "}") - } - buf.WriteString(" 0") - buf.WriteString("\n") - return - } - // otherwise iterate through values and write one line per label - for _, l := range counter.values { - buf.WriteString(counter.name) - buf.WriteString("{") - if len(parentLabels) > 0 { - buf.WriteString(parentLabels) - if len(l.formattedLabels) > 0 { - buf.WriteString(",") - } - } - buf.WriteString(l.formattedLabels) - buf.WriteString("} ") - value := l.counter - if len(l.labels) == 0 { - value += counter.intValue.Load() - } - buf.WriteString(strconv.FormatUint(value, 10)) - buf.WriteString("\n") - } + counter.c.writeMetric(buf, "counter", parentLabels) } // AddMetric adds the metric into the map func (counter *Counter) AddMetric(values map[string]float64) { - counter.Lock() - defer counter.Unlock() - - if len(counter.values) < 1 { - return - } - - for _, l := range counter.values { - sum := l.counter - if len(l.labels) == 0 { - sum += counter.intValue.Load() - } - var suffix string - if len(l.formattedLabels) > 0 { - suffix = ":" + l.formattedLabels - } - values[sanitizeTelemetryName(counter.name+suffix)] = float64(sum) - } + counter.c.addMetric(values) } diff --git a/util/metrics/counterCommon.go b/util/metrics/counterCommon.go deleted file mode 100644 index 2ce5468672..0000000000 --- a/util/metrics/counterCommon.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2019-2024 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 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) - intValue atomic.Uint64 - - deadlock.Mutex - name string - description string - values []*counterValues - labels map[string]int // map each label ( i.e. httpErrorCode ) to an index. - valuesIndices map[int]int -} - -type counterValues struct { - counter uint64 - labels map[string]string - formattedLabels string -} diff --git a/util/metrics/counter_test.go b/util/metrics/counter_test.go index 1e1fa2e16b..c987624cb7 100644 --- a/util/metrics/counter_test.go +++ b/util/metrics/counter_test.go @@ -231,3 +231,42 @@ func TestGetValueForLabels(t *testing.T) { c.Inc(labels2) require.Equal(t, uint64(1), c.GetUint64ValueForLabels(labels2)) } + +func TestCounterLabels(t *testing.T) { + partitiontest.PartitionTest(t) + + m := MakeCounter(MetricName{Name: "testname", Description: "testhelp"}) + m.Deregister(nil) + + m.AddUint64(1, map[string]string{"a": "b"}) + m.AddUint64(10, map[string]string{"c": "d"}) + m.AddUint64(1, map[string]string{"a": "b"}) + m.AddUint64(5, nil) + + require.Equal(t, uint64(2), m.GetUint64ValueForLabels(map[string]string{"a": "b"})) + require.Equal(t, uint64(10), m.GetUint64ValueForLabels(map[string]string{"c": "d"})) + + buf := strings.Builder{} + m.WriteMetric(&buf, "") + res := buf.String() + require.Contains(t, res, `testname{a="b"} 2`) + require.Contains(t, res, `testname{c="d"} 10`) + require.Contains(t, res, `testname 5`) + require.Equal(t, 1, strings.Count(res, "# HELP testname testhelp")) + require.Equal(t, 1, strings.Count(res, "# TYPE testname counter")) + + buf = strings.Builder{} + m.WriteMetric(&buf, `p1=v1,p2="v2"`) + res = buf.String() + require.Contains(t, res, `testname{p1=v1,p2="v2",a="b"} 2`) + require.Contains(t, res, `testname{p1=v1,p2="v2",c="d"} 10`) + + m = MakeCounter(MetricName{Name: "testname2", Description: "testhelp2"}) + m.Deregister(nil) + + m.AddUint64(101, nil) + buf = strings.Builder{} + m.WriteMetric(&buf, "") + res = buf.String() + require.Contains(t, res, `testname2 101`) +} diff --git a/util/metrics/gauge.go b/util/metrics/gauge.go index 6b43075ef7..bbc143a14f 100644 --- a/util/metrics/gauge.go +++ b/util/metrics/gauge.go @@ -17,24 +17,23 @@ package metrics import ( - "strconv" "strings" - "sync/atomic" ) // Gauge represent a single gauge variable. type Gauge struct { - value atomic.Uint64 - name string - description string + g couge } // MakeGauge create a new gauge with the provided name and description. func MakeGauge(metric MetricName) *Gauge { - c := &Gauge{ - description: metric.Description, - name: metric.Name, - } + c := &Gauge{g: couge{ + values: make([]*cougeValues, 0), + description: metric.Description, + name: metric.Name, + labels: make(map[string]int), + valuesIndices: make(map[int]int), + }} c.Register(nil) return c } @@ -57,39 +56,32 @@ func (gauge *Gauge) Deregister(reg *Registry) { } } -// Add increases gauge by x -func (gauge *Gauge) Add(x uint64) { - gauge.value.Add(x) -} - // Set sets gauge to x func (gauge *Gauge) Set(x uint64) { - gauge.value.Store(x) + if gauge.g.intValue.Swap(x) == 0 { + // This is the first Set. Create a dummy + // counterValue for the no-labels value. + // Dummy counterValue simplifies display in WriteMetric. + gauge.g.setLabels(0, nil) + } +} + +// SetLabels sets gauge to x with labels +func (gauge *Gauge) SetLabels(x uint64, labels map[string]string) { + gauge.g.setLabels(x, labels) } // WriteMetric writes the metric into the output stream func (gauge *Gauge) WriteMetric(buf *strings.Builder, parentLabels string) { - buf.WriteString("# HELP ") - buf.WriteString(gauge.name) - buf.WriteString(" ") - buf.WriteString(gauge.description) - buf.WriteString("\n# TYPE ") - buf.WriteString(gauge.name) - buf.WriteString(" gauge\n") - buf.WriteString(gauge.name) - buf.WriteString("{") - if len(parentLabels) > 0 { - buf.WriteString(parentLabels) - } - buf.WriteString("} ") - value := gauge.value.Load() - buf.WriteString(strconv.FormatUint(value, 10)) - buf.WriteString("\n") + gauge.g.writeMetric(buf, "gauge", parentLabels) } // AddMetric adds the metric into the map func (gauge *Gauge) AddMetric(values map[string]float64) { - value := gauge.value.Load() + gauge.g.addMetric(values) +} - values[sanitizeTelemetryName(gauge.name)] = float64(value) +// GetUint64ValueForLabels returns the value of the counter for the given labels or 0 if it's not found. +func (gauge *Gauge) GetUint64ValueForLabels(labels map[string]string) uint64 { + return gauge.g.getUint64ValueForLabels(labels) } diff --git a/util/metrics/gauge_test.go b/util/metrics/gauge_test.go index 098d20193e..8470362e82 100644 --- a/util/metrics/gauge_test.go +++ b/util/metrics/gauge_test.go @@ -52,8 +52,7 @@ func TestMetricGauge(t *testing.T) { gauges[i] = MakeGauge(MetricName{Name: fmt.Sprintf("gauge_%d", i), Description: "this is the metric test for gauge object"}) } for i := 0; i < 9; i++ { - gauges[i%3].Set(uint64(i * 100)) - gauges[i%3].Add(uint64(i)) + gauges[i%3].Set(uint64(i*100 + i)) // wait half-a cycle time.Sleep(test.sampleRate / 2) } @@ -86,3 +85,42 @@ func TestMetricGauge(t *testing.T) { } } } + +func TestGaugeLabels(t *testing.T) { + partitiontest.PartitionTest(t) + + m := MakeGauge(MetricName{Name: "testname", Description: "testhelp"}) + m.Deregister(nil) + + m.SetLabels(1, map[string]string{"a": "b"}) + m.SetLabels(10, map[string]string{"c": "d"}) + m.SetLabels(2, map[string]string{"a": "b"}) + m.SetLabels(5, nil) + + require.Equal(t, uint64(2), m.GetUint64ValueForLabels(map[string]string{"a": "b"})) + require.Equal(t, uint64(10), m.GetUint64ValueForLabels(map[string]string{"c": "d"})) + + buf := strings.Builder{} + m.WriteMetric(&buf, "") + res := buf.String() + require.Contains(t, res, `testname{a="b"} 2`) + require.Contains(t, res, `testname{c="d"} 10`) + require.Contains(t, res, `testname 5`) + require.Equal(t, 1, strings.Count(res, "# HELP testname testhelp")) + require.Equal(t, 1, strings.Count(res, "# TYPE testname gauge")) + + buf = strings.Builder{} + m.WriteMetric(&buf, `p1=v1,p2="v2"`) + res = buf.String() + require.Contains(t, res, `testname{p1=v1,p2="v2",a="b"} 2`) + require.Contains(t, res, `testname{p1=v1,p2="v2",c="d"} 10`) + + m = MakeGauge(MetricName{Name: "testname2", Description: "testhelp2"}) + m.Deregister(nil) + + m.Set(101) + buf = strings.Builder{} + m.WriteMetric(&buf, "") + res = buf.String() + require.Contains(t, res, `testname2 101`) +} From 46ef82fc738af0b8a7b1b460d7810e762b6545cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:50:01 -0400 Subject: [PATCH 102/218] Build: bump dash from 2.11.1 to 2.15.0 in /test/heapwatch (#5928) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/heapwatch/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt index d8ee41d7e6..9743ce85c1 100644 --- a/test/heapwatch/requirements.txt +++ b/test/heapwatch/requirements.txt @@ -1,4 +1,4 @@ -dash==2.11.1 +dash==2.15.0 dash-table==5.0.0 Jinja2==3.1.3 matplotlib==3.7.2 From c2d7047585f6109d866ebaf9fca0ee7490b16c6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 11:24:13 -0400 Subject: [PATCH 103/218] Build: bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#5953) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 403d02010b..b3541ed91b 100644 --- a/go.mod +++ b/go.mod @@ -164,7 +164,7 @@ require ( golang.org/x/term v0.15.0 // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect golang.org/x/tools v0.11.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/go.sum b/go.sum index 63ef5eb7b9..a7f32e1ac7 100644 --- a/go.sum +++ b/go.sum @@ -1077,8 +1077,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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= From 0e7087bb75efb14fe0d03009212a8c1111014102 Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:32:37 -0400 Subject: [PATCH 104/218] build: support SWAGGER_CONVERTER_API override (#5968) --- .github/workflows/codegen_verification.yml | 7 +++++++ daemon/algod/api/Makefile | 5 ++++- daemon/algod/api/README.md | 7 ++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codegen_verification.yml b/.github/workflows/codegen_verification.yml index 362f0c7add..a01e3e5e9c 100644 --- a/.github/workflows/codegen_verification.yml +++ b/.github/workflows/codegen_verification.yml @@ -7,6 +7,11 @@ on: jobs: codegen_verification: runs-on: ubuntu-20.04 + services: + converter: + image: swaggerapi/swagger-converter@sha256:dcfd1c2537f5f271cb4ec942d08aa59ca41b9a24078040061a772afca7e548ae # v1.0.4 + ports: + - 8080:8080 steps: - name: Check out code uses: actions/checkout@v3.5.3 @@ -16,6 +21,8 @@ jobs: - name: Uninstall existing go installation run: sudo apt-get -y -q purge golang-go - name: Run codegen_verification.sh + env: + SWAGGER_CONVERTER_API: "http://localhost:8080" run: | export GOPATH="${GITHUB_WORKSPACE}/go" cd go-algorand diff --git a/daemon/algod/api/Makefile b/daemon/algod/api/Makefile index c4104d9e80..000825079e 100644 --- a/daemon/algod/api/Makefile +++ b/daemon/algod/api/Makefile @@ -1,6 +1,9 @@ GOPATH := $(shell go env GOPATH) GOPATH1 := $(firstword $(subst :, ,$(GOPATH))) +# Allow overriding swagger-converter API, e.g. for use with local container +SWAGGER_CONVERTER_API ?= https://converter.swagger.io + # `make all` or just `make` should be appropriate for dev work all: server/v2/generated/model/types.go server/v2/generated/nonparticipating/public/routes.go server/v2/generated/nonparticipating/private/routes.go server/v2/generated/participating/public/routes.go server/v2/generated/participating/private/routes.go server/v2/generated/data/routes.go server/v2/generated/experimental/routes.go @@ -30,7 +33,7 @@ server/v2/generated/model/types.go: algod.oas3.yml $(GOPATH1)/bin/oapi-codegen -config ./server/v2/generated/model/model_types.yml algod.oas3.yml algod.oas3.yml: algod.oas2.json - curl -s -X POST "https://converter.swagger.io/api/convert" -H "accept: application/json" -H "Content-Type: application/json" -d @./algod.oas2.json -o .3tmp.json + curl -s -X POST "$(SWAGGER_CONVERTER_API)/api/convert" -H "accept: application/json" -H "Content-Type: application/json" -d @./algod.oas2.json -o .3tmp.json python3 jsoncanon.py < .3tmp.json > algod.oas3.yml rm -f .3tmp.json diff --git a/daemon/algod/api/README.md b/daemon/algod/api/README.md index 482ba23004..f8b9cc48c4 100644 --- a/daemon/algod/api/README.md +++ b/daemon/algod/api/README.md @@ -49,4 +49,9 @@ Specifically, `uint64` types aren't strictly supported by OpenAPI. So we added a ## Why do we have algod.oas2.json and algod.oas3.yml? -We chose to maintain V2 and V3 versions of the spec because OpenAPI v3 doesn't seem to be widely supported. Some tools worked better with V3 and others with V2, so having both available has been useful. To reduce developer burdon, the v2 specfile is automatically converted v3 using [converter.swagger.io](http://converter.swagger.io/). +We chose to maintain V2 and V3 versions of the spec because OpenAPI v3 doesn't seem to be widely supported. Some tools worked better with V3 and others with V2, so having both available has been useful. To reduce developer burden, the v2 specfile is automatically converted v3 using [converter.swagger.io](http://converter.swagger.io/). + +If you want to run the converter locally, you can build the [swagger-converter](https://github.com/swagger-api/swagger-converter) project or run its [docker image](https://hub.docker.com/r/swaggerapi/swagger-converter) and specify the `SWAGGER_CONVERTER_API` environment variable when using this Makefile, for example by running: +``` +SWAGGER_CONVERTER_API=http://localhost:8080 make +``` From dfd95ffee9756c957b09e6f3ba28c1cded893871 Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 4 Apr 2024 16:00:20 -0400 Subject: [PATCH 105/218] Tools: Revert "feat: add support to display mnemonic discreetly for `algokey generate`" (#5974) --- cmd/algokey/common.go | 16 ---------------- cmd/algokey/common_test.go | 39 -------------------------------------- cmd/algokey/generate.go | 12 +----------- go.mod | 5 ----- go.sum | 10 ---------- 5 files changed, 1 insertion(+), 81 deletions(-) delete mode 100644 cmd/algokey/common_test.go diff --git a/cmd/algokey/common.go b/cmd/algokey/common.go index 0259dc5e05..8fc5867db9 100644 --- a/cmd/algokey/common.go +++ b/cmd/algokey/common.go @@ -23,7 +23,6 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/passphrase" - "github.com/muesli/termenv" ) const ( @@ -123,18 +122,3 @@ func readFile(filename string) ([]byte, error) { } return os.ReadFile(filename) } - -// printDiscreetly Print a secret string to an alternate screen, -// so the string isn't printed to the terminal. -func printDiscreetly(w io.Writer, promptMsg, secretMsg string) error { - output := termenv.NewOutput(w) - output.AltScreen() - defer output.ExitAltScreen() - if _, err := fmt.Fprintf(output, "%s\n\n%s\n\nPress 'Enter' key to continue.", promptMsg, secretMsg); err != nil { - return err - } - if _, err := fmt.Scanln(); err != nil { - return err - } - return nil -} diff --git a/cmd/algokey/common_test.go b/cmd/algokey/common_test.go deleted file mode 100644 index 2256342616..0000000000 --- a/cmd/algokey/common_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2019-2024 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 main - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/test/partitiontest" -) - -func Test_printDiscreetly(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - buf := new(bytes.Buffer) - var ( - promptMsg = "Prompt Message" - secretMsg = "Secret Message" - ) - require.NoError(t, printDiscreetly(buf, promptMsg, secretMsg)) - require.Contains(t, buf.String(), promptMsg) - require.Contains(t, buf.String(), secretMsg) -} diff --git a/cmd/algokey/generate.go b/cmd/algokey/generate.go index 6b1c409e68..6382fa3a0c 100644 --- a/cmd/algokey/generate.go +++ b/cmd/algokey/generate.go @@ -18,7 +18,6 @@ package main import ( "fmt" - "os" "github.com/spf13/cobra" @@ -28,12 +27,10 @@ import ( var generateKeyfile string var generatePubkeyfile string -var generateDiscreet bool func init() { generateCmd.Flags().StringVarP(&generateKeyfile, "keyfile", "f", "", "Private key filename") generateCmd.Flags().StringVarP(&generatePubkeyfile, "pubkeyfile", "p", "", "Public key filename") - generateCmd.Flags().BoolVar(&generateDiscreet, "discreet", false, "Print mnemonic discreetly to an alternate screen") } var generateCmd = &cobra.Command{ @@ -49,14 +46,7 @@ var generateCmd = &cobra.Command{ key := crypto.GenerateSignatureSecrets(seed) publicKeyChecksummed := basics.Address(key.SignatureVerifier).String() - if generateDiscreet { - if err := printDiscreetly(os.Stderr, "**Important** write this private key mnemonic phrase in a safe place. Do not share it to anyone", fmt.Sprintf("Private key mnemonic: %s", mnemonic)); err != nil { - fmt.Fprintf(os.Stderr, "Fail to print mnemonic: %v", err) - os.Exit(1) - } - } else { - fmt.Printf("Private key mnemonic: %s\n", mnemonic) - } + fmt.Printf("Private key mnemonic: %s\n", mnemonic) fmt.Printf("Public key: %s\n", publicKeyChecksummed) if generateKeyfile != "" { diff --git a/go.mod b/go.mod index b3541ed91b..989a3021fb 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,6 @@ require ( github.com/libp2p/go-yamux/v4 v4.0.1 github.com/mattn/go-sqlite3 v1.14.16 github.com/miekg/dns v1.1.55 - github.com/muesli/termenv v0.15.2 github.com/multiformats/go-multiaddr v0.10.1 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olivere/elastic v6.2.14+incompatible @@ -52,7 +51,6 @@ require ( require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // 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 @@ -109,12 +107,10 @@ require ( 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/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -146,7 +142,6 @@ require ( 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/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index a7f32e1ac7..89b811f956 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,6 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 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= @@ -432,8 +430,6 @@ github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjP 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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 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= @@ -455,8 +451,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky 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-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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= @@ -497,8 +491,6 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 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= @@ -599,8 +591,6 @@ github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2Ep 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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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= From c07f2d529a88c72b6edb0e649f2baf59c9459cab Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Wed, 17 Apr 2024 16:12:49 -0400 Subject: [PATCH 106/218] Utils: Renaming files across devices (#5977) Co-authored-by: ohill <145173879+ohill@users.noreply.github.com> --- config/localTemplate.go | 3 +- logging/cyclicWriter.go | 3 +- logging/cyclicWriter_test.go | 54 +++++++++++- util/io.go | 65 ++++++++++++++ util/io_test.go | 166 ++++++++++++++++++++++++++++++++++- 5 files changed, 282 insertions(+), 9 deletions(-) diff --git a/config/localTemplate.go b/config/localTemplate.go index ce4294de01..67218f8ade 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -24,6 +24,7 @@ import ( "time" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/codecs" ) @@ -893,7 +894,7 @@ func moveDirIfExists(logger logger, srcdir, dstdir string, files ...string) erro // then, check if any files exist in srcdir, and move them to dstdir for _, file := range files { if _, err := os.Stat(filepath.Join(srcdir, file)); err == nil { - if err := os.Rename(filepath.Join(srcdir, file), filepath.Join(dstdir, file)); err != nil { + if err := util.MoveFile(filepath.Join(srcdir, file), filepath.Join(dstdir, file)); err != nil { return fmt.Errorf("failed to move file %s from %s to %s: %v", file, srcdir, dstdir, err) } logger.Infof("Moved DB file %s from ColdDataDir %s to HotDataDir %s", file, srcdir, dstdir) diff --git a/logging/cyclicWriter.go b/logging/cyclicWriter.go index d15782c93b..579fb4f881 100644 --- a/logging/cyclicWriter.go +++ b/logging/cyclicWriter.go @@ -26,6 +26,7 @@ import ( "text/template" "time" + "github.com/algorand/go-algorand/util" "github.com/algorand/go-deadlock" ) @@ -173,7 +174,7 @@ func (cyclic *CyclicFileWriter) Write(p []byte) (n int, err error) { shouldBz2 = true archivePath = archivePath[:len(archivePath)-4] } - if err = os.Rename(cyclic.liveLog, archivePath); err != nil { + if err = util.MoveFile(cyclic.liveLog, archivePath); err != nil { panic(fmt.Sprintf("CyclicFileWriter: cannot archive full log %v", err)) } if shouldGz { diff --git a/logging/cyclicWriter_test.go b/logging/cyclicWriter_test.go index 1d5de4bb1a..1149ae2098 100644 --- a/logging/cyclicWriter_test.go +++ b/logging/cyclicWriter_test.go @@ -18,16 +18,19 @@ package logging import ( "os" + "os/exec" + "path/filepath" + "runtime" + "strings" "testing" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) -func TestCyclicWrite(t *testing.T) { - partitiontest.PartitionTest(t) - liveFileName := "live.test" - archiveFileName := "archive.test" +func testCyclicWrite(t *testing.T, liveFileName, archiveFileName string) { + t.Helper() + defer os.Remove(liveFileName) defer os.Remove(archiveFileName) @@ -60,3 +63,46 @@ func TestCyclicWrite(t *testing.T) { require.Equal(t, byte('A'), oldData[i]) } } + +func TestCyclicWrite(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tmpDir := t.TempDir() + + liveFileName := filepath.Join(tmpDir, "live.test") + archiveFileName := filepath.Join(tmpDir, "archive.test") + + testCyclicWrite(t, liveFileName, archiveFileName) +} + +func execCommand(t *testing.T, cmdAndArsg ...string) { + t.Helper() + + cmd := exec.Command(cmdAndArsg[0], cmdAndArsg[1:]...) + var errOutput strings.Builder + cmd.Stderr = &errOutput + err := cmd.Run() + require.NoError(t, err, errOutput.String()) +} + +func TestCyclicWriteAcrossFilesystems(t *testing.T) { + partitiontest.PartitionTest(t) + + isLinux := strings.HasPrefix(runtime.GOOS, "linux") + + // Skip unless CIRCLECI or TEST_MOUNT_TMPFS is set, and we are on a linux system + if !isLinux || (os.Getenv("CIRCLECI") == "" && os.Getenv("TEST_MOUNT_TMPFS") == "") { + t.Skip("This test must be run on a linux system with administrator privileges") + } + + mountDir := t.TempDir() + execCommand(t, "sudo", "mount", "-t", "tmpfs", "-o", "size=2K", "tmpfs", mountDir) + + defer execCommand(t, "sudo", "umount", mountDir) + + liveFileName := filepath.Join(t.TempDir(), "live.test") + archiveFileName := filepath.Join(mountDir, "archive.test") + + testCyclicWrite(t, liveFileName, archiveFileName) +} diff --git a/util/io.go b/util/io.go index f8290aec40..43e47d3f49 100644 --- a/util/io.go +++ b/util/io.go @@ -24,6 +24,71 @@ import ( "strings" ) +// MoveFile moves a file from src to dst. The advantages of using this over +// os.Rename() is that it can move files across different filesystems. +func MoveFile(src, dst string) error { + err := os.Rename(src, dst) + if err != nil { + // os.Rename() may have failed because src and dst are on different + // filesystems. Let's try to move the file by copying and deleting the + // source file. + return moveFileByCopying(src, dst) + } + return err +} + +func moveFileByCopying(src, dst string) error { + // Lstat is specifically used to detect if src is a symlink. We could + // support moving symlinks by deleting src and creating a new symlink at + // dst, but we don't currently expect to encounter that case, so it has not + // been implemented. + srcInfo, srcErr := os.Lstat(src) + if srcErr != nil { + return srcErr + } + if !srcInfo.Mode().IsRegular() { + return fmt.Errorf("cannot move source file '%s': it is not a regular file (%v)", src, srcInfo.Mode()) + } + + if dstInfo, dstErr := os.Lstat(dst); dstErr == nil { + if dstInfo.Mode().IsDir() { + return fmt.Errorf("cannot move source file '%s' to destination '%s': destination is a directory", src, dst) + } + if os.SameFile(dstInfo, srcInfo) { + return fmt.Errorf("cannot move source file '%s' to destination '%s': source and destination are the same file", src, dst) + } + } + + dstDir := filepath.Dir(dst) + dstBase := filepath.Base(dst) + + tmpDstFile, errTmp := os.CreateTemp(dstDir, dstBase+".tmp-") + if errTmp != nil { + return errTmp + } + tmpDst := tmpDstFile.Name() + if errClose := tmpDstFile.Close(); errClose != nil { + return errClose + } + + if _, err := CopyFile(src, tmpDst); err != nil { + // If the copy fails, try to clean up the temporary file + _ = os.Remove(tmpDst) + return err + } + if err := os.Rename(tmpDst, dst); err != nil { + // If the rename fails, try to clean up the temporary file + _ = os.Remove(tmpDst) + return err + } + if err := os.Remove(src); err != nil { + // Don't try to clean up the destination file here. Duplicate data is + // better than lost/incomplete data. + return fmt.Errorf("failed to remove source file '%s' after moving it to '%s': %w", src, dst, err) + } + return nil +} + // CopyFile uses io.Copy() to copy a file to another location // This was copied from https://opensource.com/article/18/6/copying-files-go func CopyFile(src, dst string) (int64, error) { diff --git a/util/io_test.go b/util/io_test.go index 6f9f6dcfdd..3c587286f7 100644 --- a/util/io_test.go +++ b/util/io_test.go @@ -17,25 +17,185 @@ package util import ( + "fmt" "os" - "path" + "os/exec" + "path/filepath" + "runtime" + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/test/partitiontest" ) func TestIsEmpty(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() - testPath := path.Join(os.TempDir(), "this", "is", "a", "long", "path") + testPath := filepath.Join(t.TempDir(), "this", "is", "a", "long", "path") err := os.MkdirAll(testPath, os.ModePerm) assert.NoError(t, err) defer os.RemoveAll(testPath) assert.True(t, IsEmpty(testPath)) - _, err = os.Create(path.Join(testPath, "file.txt")) + _, err = os.Create(filepath.Join(testPath, "file.txt")) assert.NoError(t, err) assert.False(t, IsEmpty(testPath)) } + +func testMoveFileSimple(t *testing.T, src, dst string) { + t.Helper() + + require.NoFileExists(t, src) + require.NoFileExists(t, dst) + + defer os.Remove(src) + defer os.Remove(dst) + + f, err := os.Create(src) + require.NoError(t, err) + + _, err = f.WriteString("test file contents") + require.NoError(t, err) + require.NoError(t, f.Close()) + + err = MoveFile(src, dst) + require.NoError(t, err) + + require.FileExists(t, dst) + require.NoFileExists(t, src) + + dstContents, err := os.ReadFile(dst) + require.NoError(t, err) + assert.Equal(t, "test file contents", string(dstContents)) +} + +func TestMoveFile(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tmpDir := t.TempDir() + + src := filepath.Join(tmpDir, "src.txt") + dst := filepath.Join(tmpDir, "dst.txt") + testMoveFileSimple(t, src, dst) +} + +func execCommand(t *testing.T, cmdAndArsg ...string) { + t.Helper() + + cmd := exec.Command(cmdAndArsg[0], cmdAndArsg[1:]...) + var errOutput strings.Builder + cmd.Stderr = &errOutput + err := cmd.Run() + require.NoError(t, err, errOutput.String()) +} + +func TestMoveFileAcrossFilesystems(t *testing.T) { + partitiontest.PartitionTest(t) + + isLinux := strings.HasPrefix(runtime.GOOS, "linux") + + // Skip unless CIRCLECI or TEST_MOUNT_TMPFS is set, and we are on a linux system + if !isLinux || (os.Getenv("CIRCLECI") == "" && os.Getenv("TEST_MOUNT_TMPFS") == "") { + t.Skip("This test must be run on a linux system with administrator privileges") + } + + mountDir := t.TempDir() + execCommand(t, "sudo", "mount", "-t", "tmpfs", "-o", "size=1K", "tmpfs", mountDir) + + defer execCommand(t, "sudo", "umount", mountDir) + + src := filepath.Join(t.TempDir(), "src.txt") + dst := filepath.Join(mountDir, "dst.txt") + + testMoveFileSimple(t, src, dst) +} + +func TestMoveFileSourceDoesNotExist(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tmpDir := t.TempDir() + + src := filepath.Join(tmpDir, "src.txt") + dst := filepath.Join(tmpDir, "dst.txt") + + err := MoveFile(src, dst) + var pathError *os.PathError + require.ErrorAs(t, err, &pathError) + require.Equal(t, "lstat", pathError.Op) + require.Equal(t, src, pathError.Path) +} + +func TestMoveFileSourceIsASymlink(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tmpDir := t.TempDir() + + root := filepath.Join(tmpDir, "root.txt") + src := filepath.Join(tmpDir, "src.txt") + dst := filepath.Join(tmpDir, "dst.txt") + + _, err := os.Create(root) + require.NoError(t, err) + + err = os.Symlink(root, src) + require.NoError(t, err) + + // os.Rename should work in this case + err = MoveFile(src, dst) + require.NoError(t, err) + + // Undo the move + require.NoError(t, MoveFile(dst, src)) + + // But our moveFileByCopying should fail, since we haven't implemented this case + err = moveFileByCopying(src, dst) + require.ErrorContains(t, err, fmt.Sprintf("cannot move source file '%s': it is not a regular file", src)) +} + +func TestMoveFileSourceAndDestinationAreSame(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tmpDir := t.TempDir() + require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "folder"), os.ModePerm)) + + src := filepath.Join(tmpDir, "src.txt") + dst := src[:len(src)-len("src.txt")] + "folder/../src.txt" + + // dst refers to the same file as src, but with a different path + require.NotEqual(t, src, dst) + require.Equal(t, src, filepath.Clean(dst)) + + _, err := os.Create(src) + require.NoError(t, err) + + // os.Rename can handle this case, but our moveFileByCopying should fail + err = moveFileByCopying(src, dst) + require.ErrorContains(t, err, fmt.Sprintf("cannot move source file '%s' to destination '%s': source and destination are the same file", src, dst)) +} + +func TestMoveFileDestinationIsADirectory(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + tmpDir := t.TempDir() + + src := filepath.Join(tmpDir, "src.txt") + dst := filepath.Join(tmpDir, "dst.txt") + + _, err := os.Create(src) + require.NoError(t, err) + + err = os.Mkdir(dst, os.ModePerm) + require.NoError(t, err) + + err = MoveFile(src, dst) + require.ErrorContains(t, err, fmt.Sprintf("cannot move source file '%s' to destination '%s': destination is a directory", src, dst)) +} From b259709f4deb501ef1df156063f0142c81fa4635 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 23 Apr 2024 13:43:39 -0400 Subject: [PATCH 107/218] Incentives: Suspend "absentee" accounts that don't propose. (#5757) Co-authored-by: Jason Paulos Co-authored-by: cce <51567+cce@users.noreply.github.com> Co-authored-by: chris erway --- agreement/abstractions.go | 38 +- agreement/agreementtest/simulate_test.go | 14 +- agreement/common_test.go | 23 +- agreement/fuzzer/ledger_test.go | 14 +- agreement/msgp_gen.go | 1801 +++++++++++------ agreement/player_permutation_test.go | 9 +- agreement/proposal.go | 121 +- agreement/proposalStore_test.go | 16 +- agreement/proposal_test.go | 15 +- agreement/pseudonode.go | 6 +- agreement/vote.go | 2 +- config/consensus.go | 122 +- crypto/onetimesig.go | 5 + crypto/vrf.go | 5 + daemon/algod/api/Makefile | 1 + daemon/algod/api/algod.oas2.json | 12 + daemon/algod/api/algod.oas3.yml | 12 + daemon/algod/api/server/v2/account.go | 24 +- daemon/algod/api/server/v2/account_test.go | 21 + .../api/server/v2/generated/data/routes.go | 258 +-- .../v2/generated/experimental/routes.go | 258 +-- .../api/server/v2/generated/model/types.go | 9 + .../nonparticipating/private/routes.go | 420 ++-- .../nonparticipating/public/routes.go | 412 ++-- .../generated/participating/private/routes.go | 270 +-- .../generated/participating/public/routes.go | 437 ++-- daemon/algod/api/server/v2/handlers.go | 10 +- data/basics/fraction.go | 69 + data/basics/fraction_test.go | 81 + data/basics/msgp_gen.go | 822 +++++--- data/basics/overflow.go | 8 + data/basics/units.go | 13 + data/basics/userBalance.go | 30 +- data/basics/userBalance_test.go | 2 + data/bookkeeping/block.go | 101 +- data/bookkeeping/block_test.go | 191 +- data/bookkeeping/msgp_gen.go | 1251 ++++++++---- data/datatest/impls.go | 20 +- data/pools/transactionPool.go | 41 +- data/pools/transactionPool_test.go | 72 +- data/transactions/logic/assembler_test.go | 6 + data/transactions/logic/eval.go | 12 +- data/transactions/logic/fields.go | 9 + data/transactions/logic/fields_string.go | 9 +- data/transactions/logic/opcodes.go | 6 +- data/transactions/payment.go | 10 +- data/transactions/transaction.go | 10 +- data/transactions/transaction_test.go | 28 +- data/txHandler_test.go | 2 +- data/txntest/txn.go | 50 +- ledger/acctdeltas.go | 74 +- ledger/acctdeltas_test.go | 21 +- ledger/acctonline.go | 1 + ledger/acctonline_expired_test.go | 5 +- ledger/acctupdates_test.go | 19 +- ledger/apply/asset_test.go | 2 +- ledger/apply/keyreg.go | 13 +- ledger/apply/keyreg_test.go | 32 +- ledger/apply/payment.go | 18 - ledger/apply/payment_test.go | 37 +- ledger/apptxn_test.go | 53 +- ledger/double_test.go | 77 +- ledger/eval/applications.go | 3 +- ledger/eval/cow.go | 5 + ledger/eval/cow_test.go | 3 +- ledger/eval/eval.go | 436 +++- ledger/eval/eval_test.go | 398 +++- .../prefetcher/prefetcher_alignment_test.go | 50 +- ledger/eval/prefetcher/prefetcher_test.go | 5 +- ledger/eval_simple_test.go | 499 ++++- ledger/evalbench_test.go | 18 +- ledger/fullblock_perf_test.go | 6 +- ledger/ledger_perf_test.go | 10 +- ledger/ledger_test.go | 139 +- ledger/ledgercore/accountdata.go | 43 +- ledger/ledgercore/statedelta.go | 2 +- ledger/ledgercore/validatedBlock.go | 63 +- ledger/lruonlineaccts_test.go | 13 +- ledger/simple_test.go | 55 +- ledger/simulation/simulator.go | 7 +- ledger/simulation/testing/utils.go | 8 +- ledger/store/trackerdb/data.go | 35 +- ledger/store/trackerdb/data_test.go | 6 +- ledger/store/trackerdb/msgp_gen.go | 120 +- ledger/store/trackerdb/sqlitedriver/schema.go | 8 +- .../trackerdb/sqlitedriver/schema_test.go | 15 +- ledger/testing/randomAccounts.go | 59 +- ledger/testing/randomAccounts_test.go | 1 - ledger/testing/testGenesis.go | 25 +- node/assemble_test.go | 8 +- node/impls.go | 5 +- node/node.go | 95 +- protocol/tags.go | 2 +- test/e2e-go/cli/goal/clerk_test.go | 4 +- .../catchup/catchpointCatchup_test.go | 12 +- .../features/incentives/payouts_test.go | 335 +++ .../features/incentives/suspension_test.go | 171 ++ .../e2e-go/features/multisig/multisig_test.go | 2 +- .../onlineOfflineParticipation_test.go | 8 +- .../participationExpiration_test.go | 4 +- .../participationRewards_test.go | 2 +- .../features/transactions/accountv2_test.go | 4 +- .../features/transactions/app_pages_test.go | 10 +- .../features/transactions/asset_test.go | 2 +- .../transactions/close_account_test.go | 4 +- .../features/transactions/proof_test.go | 4 +- .../features/transactions/sendReceive_test.go | 2 +- test/e2e-go/perf/basic_test.go | 2 +- .../upgrades/application_support_test.go | 6 +- test/e2e-go/upgrades/rekey_support_test.go | 2 +- .../upgrades/send_receive_upgrade_test.go | 4 +- test/framework/fixtures/libgoalFixture.go | 24 + test/framework/fixtures/restClientFixture.go | 18 +- test/scripts/e2e_subs/absentee.py | 38 + test/scripts/e2e_subs/eligible.py | 51 + test/scripts/e2e_subs/goal/goal.py | 26 +- test/scripts/e2e_subs/htlc-teal-test.sh | 2 +- test/scripts/e2e_subs/payouts.py | 84 + test/scripts/e2e_subs/periodic-teal-test.sh | 2 +- test/testdata/nettemplates/Payouts.json | 28 + test/testdata/nettemplates/Suspension.json | 28 + .../generator/generator_ledger.go | 13 +- tools/debug/transplanter/main.go | 8 +- 123 files changed, 7387 insertions(+), 3185 deletions(-) create mode 100644 data/basics/fraction.go create mode 100644 data/basics/fraction_test.go create mode 100644 test/e2e-go/features/incentives/payouts_test.go create mode 100644 test/e2e-go/features/incentives/suspension_test.go create mode 100755 test/scripts/e2e_subs/absentee.py create mode 100755 test/scripts/e2e_subs/eligible.py create mode 100755 test/scripts/e2e_subs/payouts.py create mode 100644 test/testdata/nettemplates/Payouts.json create mode 100644 test/testdata/nettemplates/Suspension.json diff --git a/agreement/abstractions.go b/agreement/abstractions.go index a22a3a0526..ea0a4b1eee 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -54,13 +54,6 @@ type BlockValidator interface { // and can now be recorded in the ledger. This is an optimized version of // calling EnsureBlock() on the Ledger. type ValidatedBlock interface { - // WithSeed creates a copy of this ValidatedBlock with its - // cryptographically random seed set to the given value. - // - // Calls to Seed() or to Digest() on the copy's Block must - // reflect the value of the new seed. - WithSeed(committee.Seed) ValidatedBlock - // Block returns the underlying block that has been validated. Block() bookkeeping.Block } @@ -72,21 +65,42 @@ var ErrAssembleBlockRoundStale = errors.New("requested round for AssembleBlock i // An BlockFactory produces an Block which is suitable for proposal for a given // Round. type BlockFactory interface { - // AssembleBlock produces a new ValidatedBlock which is suitable for proposal - // at a given Round. + // AssembleBlock produces a new UnfinishedBlock for a given Round. + // It must be finalized before proposed by agreement. It is provided + // a list of participating addresses that may propose this block. // - // AssembleBlock should produce a ValidatedBlock for which the corresponding + // AssembleBlock should produce a block for which the corresponding // BlockValidator validates (i.e. for which BlockValidator.Validate // returns true). If an insufficient number of nodes can assemble valid // entries, the agreement protocol may lose liveness. // // AssembleBlock may return an error if the BlockFactory is unable to - // produce a ValidatedBlock for the given round. If an insufficient number of + // produce an UnfinishedBlock for the given round. If an insufficient number of // nodes on the network can assemble entries, the agreement protocol may // lose liveness. - AssembleBlock(basics.Round) (ValidatedBlock, error) + AssembleBlock(rnd basics.Round, partAddresses []basics.Address) (UnfinishedBlock, error) +} + +// An UnfinishedBlock represents a Block produced by a BlockFactory +// and must be finalized before being proposed by agreement. +type UnfinishedBlock interface { + // FinishBlock creates a proposable block, having set the cryptographically + // random seed and payout related fields. + // + // Calls to Seed() or to Digest() on the copy's Block must + // reflect the value of the new seed. + FinishBlock(seed committee.Seed, proposer basics.Address, eligible bool) Block + + Round() basics.Round } +// Block (in agreement) represents an UnfinishedBlock produced by a +// BlockFactory, that was later finalized by providing the seed and the +// proposer, and can now be proposed by agreement. +// +//msgp:ignore Block +type Block bookkeeping.Block + // A Ledger represents the sequence of Entries agreed upon by the protocol. // The Ledger consists of two parts: a LedgerReader and a LedgerWriter, which // provide read and write access to the ledger, respectively. diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go index a0afca4d46..6c071ed8a8 100644 --- a/agreement/agreementtest/simulate_test.go +++ b/agreement/agreementtest/simulate_test.go @@ -79,9 +79,17 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +func (b testValidatedBlock) Round() basics.Round { + return b.Inside.Round() +} + +func (b testValidatedBlock) FinishBlock(s committee.Seed, proposer basics.Address, eligible bool) agreement.Block { b.Inside.BlockHeader.Seed = s - return b + b.Inside.BlockHeader.Proposer = proposer + if !eligible { + b.Inside.BlockHeader.ProposerPayout = basics.MicroAlgos{} + } + return agreement.Block(b.Inside) } type testBlockValidator struct{} @@ -94,7 +102,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (agreement.ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round, addrs []basics.Address) (agreement.UnfinishedBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } diff --git a/agreement/common_test.go b/agreement/common_test.go index 9ec85618e8..ca8983705e 100644 --- a/agreement/common_test.go +++ b/agreement/common_test.go @@ -165,9 +165,17 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) ValidatedBlock { +func (b testValidatedBlock) Round() basics.Round { + return b.Inside.Round() +} + +func (b testValidatedBlock) FinishBlock(s committee.Seed, proposer basics.Address, eligible bool) Block { b.Inside.BlockHeader.Seed = s - return b + b.Inside.BlockHeader.Proposer = proposer + if !eligible { + b.Inside.BlockHeader.ProposerPayout = basics.MicroAlgos{} + } + return Block(b.Inside) } type testBlockValidator struct{} @@ -180,7 +188,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round, _ []basics.Address) (UnfinishedBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } @@ -413,7 +421,7 @@ type testAccountData struct { } func makeProposalsTesting(accs testAccountData, round basics.Round, period period, factory BlockFactory, ledger Ledger) (ps []proposal, vs []vote) { - ve, err := factory.AssembleBlock(round) + ve, err := factory.AssembleBlock(round, accs.addresses) if err != nil { logging.Base().Errorf("Could not generate a proposal for round %d: %v", round, err) return nil, nil @@ -525,11 +533,12 @@ func (v *voteMakerHelper) MakeRandomProposalValue() *proposalValue { func (v *voteMakerHelper) MakeRandomProposalPayload(t *testing.T, r round) (*proposal, *proposalValue) { f := testBlockFactory{Owner: 1} - ve, err := f.AssembleBlock(r) + ub, err := f.AssembleBlock(r, nil) require.NoError(t, err) + pb := ub.FinishBlock(committee.Seed{}, basics.Address{}, false) var payload unauthenticatedProposal - payload.Block = ve.Block() + payload.Block = bookkeeping.Block(pb) payload.SeedProof = randomVRFProof() propVal := proposalValue{ @@ -537,7 +546,7 @@ func (v *voteMakerHelper) MakeRandomProposalPayload(t *testing.T, r round) (*pro EncodingDigest: crypto.HashObj(payload), } - return &proposal{unauthenticatedProposal: payload, ve: ve}, &propVal + return &proposal{unauthenticatedProposal: payload}, &propVal } // make a vote for a fixed proposal value diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go index 00ba132294..efd68ae087 100644 --- a/agreement/fuzzer/ledger_test.go +++ b/agreement/fuzzer/ledger_test.go @@ -93,9 +93,17 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +func (b testValidatedBlock) Round() basics.Round { + return b.Inside.Round() +} + +func (b testValidatedBlock) FinishBlock(s committee.Seed, proposer basics.Address, eligible bool) agreement.Block { b.Inside.BlockHeader.Seed = s - return b + b.Inside.BlockHeader.Proposer = proposer + if !eligible { + b.Inside.BlockHeader.ProposerPayout = basics.MicroAlgos{} + } + return agreement.Block(b.Inside) } type testBlockValidator struct{} @@ -108,7 +116,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (agreement.ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round, _ []basics.Address) (agreement.UnfinishedBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } diff --git a/agreement/msgp_gen.go b/agreement/msgp_gen.go index 16679ce797..b012f66da2 100644 --- a/agreement/msgp_gen.go +++ b/agreement/msgp_gen.go @@ -4493,183 +4493,225 @@ func PlayerMaxSize() (s int) { func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0004Len := uint32(29) - var zb0004Mask uint64 /* 38 bits */ + zb0005Len := uint32(34) + var zb0005Mask uint64 /* 43 bits */ + if (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x40 + } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { - zb0004Len-- - zb0004Mask |= 0x40 + zb0005Len-- + zb0005Mask |= 0x80 + } + if (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x100 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80 + zb0005Len-- + zb0005Mask |= 0x200 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0 { - zb0004Len-- - zb0004Mask |= 0x100 + zb0005Len-- + zb0005Mask |= 0x400 } if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "" { - zb0004Len-- - zb0004Mask |= 0x200 + zb0005Len-- + zb0005Mask |= 0x800 } if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400 + zb0005Len-- + zb0005Mask |= 0x1000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800 + zb0005Len-- + zb0005Mask |= 0x2000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000 + zb0005Len-- + zb0005Mask |= 0x4000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000 + zb0005Len-- + zb0005Mask |= 0x8000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { - zb0004Len-- - zb0004Mask |= 0x4000 + zb0005Len-- + zb0005Mask |= 0x10000 } if (*z).unauthenticatedProposal.OriginalPeriod == 0 { - zb0004Len-- - zb0004Mask |= 0x8000 + zb0005Len-- + zb0005Mask |= 0x20000 } if (*z).unauthenticatedProposal.OriginalProposer.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x10000 + zb0005Len-- + zb0005Mask |= 0x40000 + } + if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0 { + zb0005Len-- + zb0005Mask |= 0x80000 } if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { - zb0004Len-- - zb0004Mask |= 0x20000 + zb0005Len-- + zb0005Mask |= 0x100000 + } + if (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x200000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40000 + zb0005Len-- + zb0005Mask |= 0x400000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80000 + zb0005Len-- + zb0005Mask |= 0x800000 + } + if (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x1000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0 { - zb0004Len-- - zb0004Mask |= 0x100000 + zb0005Len-- + zb0005Mask |= 0x2000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400000 + zb0005Len-- + zb0005Mask |= 0x8000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800000 + zb0005Len-- + zb0005Mask |= 0x10000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000000 + zb0005Len-- + zb0005Mask |= 0x20000000 } if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000000 + zb0005Len-- + zb0005Mask |= 0x40000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x4000000 + zb0005Len-- + zb0005Mask |= 0x80000000 } if len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0 { - zb0004Len-- - zb0004Mask |= 0x8000000 + zb0005Len-- + zb0005Mask |= 0x100000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0 { - zb0004Len-- - zb0004Mask |= 0x10000000 + zb0005Len-- + zb0005Mask |= 0x200000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0 { - zb0004Len-- - zb0004Mask |= 0x20000000 + zb0005Len-- + zb0005Mask |= 0x400000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40000000 + zb0005Len-- + zb0005Mask |= 0x800000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80000000 + zb0005Len-- + zb0005Mask |= 0x1000000000 } if (*z).unauthenticatedProposal.Block.Payset.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x100000000 + zb0005Len-- + zb0005Mask |= 0x2000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200000000 + zb0005Len-- + zb0005Mask |= 0x4000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400000000 + zb0005Len-- + zb0005Mask |= 0x8000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false { - zb0004Len-- - zb0004Mask |= 0x800000000 - } - // variable map header, size zb0004Len - o = msgp.AppendMapHeader(o, zb0004Len) - if zb0004Len != 0 { - if (zb0004Mask & 0x40) == 0 { // if not empty + zb0005Len-- + zb0005Mask |= 0x10000000000 + } + // variable map header, size zb0005Len + o = msgp.AppendMapHeader(o, zb0005Len) + if zb0005Len != 0 { + if (zb0005Mask & 0x40) == 0 { // if not empty + // string "bi" + o = append(o, 0xa2, 0x62, 0x69) + o = (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.MarshalMsg(o) + } + if (zb0005Mask & 0x80) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x80) == 0 { // if not empty + if (zb0005Mask & 0x100) == 0 { // if not empty + // string "fc" + o = append(o, 0xa2, 0x66, 0x63) + o = (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.MarshalMsg(o) + } + if (zb0005Mask & 0x200) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0005Mask & 0x400) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0005Mask & 0x800) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0005Mask & 0x1000) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0005Mask & 0x2000) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0005Mask & 0x4000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0005Mask & 0x8000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0005Mask & 0x10000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0005Mask & 0x20000) == 0 { // if not empty // string "oper" o = append(o, 0xa4, 0x6f, 0x70, 0x65, 0x72) o = msgp.AppendUint64(o, uint64((*z).unauthenticatedProposal.OriginalPeriod)) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0005Mask & 0x40000) == 0 { // if not empty // string "oprop" o = append(o, 0xa5, 0x6f, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.OriginalProposer.MarshalMsg(o) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0005Mask & 0x80000) == 0 { // if not empty + // string "partupdabs" + o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x61, 0x62, 0x73) + if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts))) + } + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + o = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].MarshalMsg(o) + } + } + if (zb0005Mask & 0x100000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -4681,47 +4723,57 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0005Mask & 0x200000) == 0 { // if not empty + // string "pp" + o = append(o, 0xa2, 0x70, 0x70) + o = (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.MarshalMsg(o) + } + if (zb0005Mask & 0x400000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0005Mask & 0x800000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0005Mask & 0x1000000) == 0 { // if not empty + // string "prp" + o = append(o, 0xa3, 0x70, 0x72, 0x70) + o = (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.MarshalMsg(o) + } + if (zb0005Mask & 0x2000000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0005Mask & 0x8000000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0005Mask & 0x10000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0005Mask & 0x20000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0005Mask & 0x40000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).unauthenticatedProposal.SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0005Mask & 0x80000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0005Mask & 0x100000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { @@ -4741,42 +4793,42 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0005Mask & 0x200000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter) } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0005Mask & 0x400000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp) } - if (zb0004Mask & 0x40000000) == 0 { // if not empty + if (zb0005Mask & 0x800000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x80000000) == 0 { // if not empty + if (zb0005Mask & 0x1000000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x100000000) == 0 { // if not empty + if (zb0005Mask & 0x2000000000) == 0 { // if not empty // string "txns" o = append(o, 0xa4, 0x74, 0x78, 0x6e, 0x73) o = (*z).unauthenticatedProposal.Block.Payset.MarshalMsg(o) } - if (zb0004Mask & 0x200000000) == 0 { // if not empty + if (zb0005Mask & 0x4000000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x400000000) == 0 { // if not empty + if (zb0005Mask & 0x8000000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x800000000) == 0 { // if not empty + if (zb0005Mask & 0x10000000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove) @@ -4799,73 +4851,73 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o st.AllowableDepth-- var field []byte _ = field - var zb0004 int - var zb0005 bool - zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Round.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Round") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Branch.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Branch") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Seed") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NativeSha512_256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Sha256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp, bts, err = msgp.ReadInt64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TimeStamp") return } } - if zb0004 > 0 { - zb0004-- - var zb0006 int - zb0006, err = msgp.ReadBytesBytesHeader(bts) + if zb0005 > 0 { + zb0005-- + var zb0007 int + zb0007, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisID") return } - if zb0006 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0006), uint64(config.MaxGenesisIDLen)) + if zb0007 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxGenesisIDLen)) return } (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -4874,157 +4926,189 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisHash") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Proposer") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "FeesCollected") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Bonus") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ProposerPayout") + return + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FeeSink") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsPool") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsLevel") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRate") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsResidue") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRecalculationRound") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CurrentProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolApprovals") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolVoteBefore") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolSwitchOn") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradePropose") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeDelay") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeApprove") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TxnCounter") return } } - if zb0004 > 0 { - zb0004-- - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0007 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0007), uint64(protocol.NumStateProofTypes)) + if zb0008 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0008), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0008 { + if zb0009 { (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0007) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0008) } - for zb0007 > 0 { + for zb0008 > 0 { var zb0001 protocol.StateProofType var zb0002 bookkeeping.StateProofTrackingData - zb0007-- + zb0008-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") @@ -5038,26 +5122,26 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } } - if zb0004 > 0 { - zb0004-- - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0009 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0010 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0010), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0010 { + if zb0011 { (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0009 { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0009] + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0010 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0010] } else { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0009) + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0010) } for zb0003 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -5067,44 +5151,73 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o } } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0012 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0013 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0012 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0012] + } else { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0012) + } + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts", zb0004) + return + } + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.Payset.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Payset") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.SeedProof.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "SeedProof") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- { - var zb0011 uint64 - zb0011, bts, err = msgp.ReadUint64Bytes(bts) + var zb0014 uint64 + zb0014, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OriginalPeriod") return } - (*z).unauthenticatedProposal.OriginalPeriod = period(zb0011) + (*z).unauthenticatedProposal.OriginalPeriod = period(zb0014) } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.OriginalProposer.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OriginalProposer") return } } - if zb0004 > 0 { - err = msgp.ErrTooManyArrayFields(zb0004) + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -5115,11 +5228,11 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o err = msgp.WrapError(err) return } - if zb0005 { + if zb0006 { (*z) = proposal{} } - for zb0004 > 0 { - zb0004-- + for zb0005 > 0 { + zb0005-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -5163,14 +5276,14 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o return } case "gen": - var zb0012 int - zb0012, err = msgp.ReadBytesBytesHeader(bts) + var zb0015 int + zb0015, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "GenesisID") return } - if zb0012 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxGenesisIDLen)) + if zb0015 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxGenesisIDLen)) return } (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -5184,6 +5297,30 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o err = msgp.WrapError(err, "GenesisHash") return } + case "prp": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Proposer") + return + } + case "fc": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "FeesCollected") + return + } + case "bi": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Bonus") + return + } + case "pp": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ProposerPayout") + return + } case "fees": bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { @@ -5275,27 +5412,27 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o return } case "spt": - var zb0013 int - var zb0014 bool - zb0013, zb0014, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0016 int + var zb0017 bool + zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "StateProofTracking") return } - if zb0013 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0013), uint64(protocol.NumStateProofTypes)) + if zb0016 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0016), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "StateProofTracking") return } - if zb0014 { + if zb0017 { (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0013) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0016) } - for zb0013 > 0 { + for zb0016 > 0 { var zb0001 protocol.StateProofType var zb0002 bookkeeping.StateProofTrackingData - zb0013-- + zb0016-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "StateProofTracking") @@ -5309,24 +5446,24 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": - var zb0015 int - var zb0016 bool - zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0018 int + var zb0019 bool + zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0015 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0018 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0018), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0016 { + if zb0019 { (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0015 { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0015] + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0018 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0018] } else { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0015) + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0018) } for zb0003 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -5335,6 +5472,33 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o return } } + case "partupdabs": + var zb0020 int + var zb0021 bool + zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0020 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0020), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0021 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0020 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0020] + } else { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0020) + } + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts", zb0004) + return + } + } case "txns": bts, err = (*z).unauthenticatedProposal.Block.Payset.UnmarshalMsgWithState(bts, st) if err != nil { @@ -5349,13 +5513,13 @@ func (z *proposal) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o } case "oper": { - var zb0017 uint64 - zb0017, bts, err = msgp.ReadUint64Bytes(bts) + var zb0022 uint64 + zb0022, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "OriginalPeriod") return } - (*z).unauthenticatedProposal.OriginalPeriod = period(zb0017) + (*z).unauthenticatedProposal.OriginalPeriod = period(zb0022) } case "oprop": bts, err = (*z).unauthenticatedProposal.OriginalProposer.UnmarshalMsgWithState(bts, st) @@ -5386,7 +5550,7 @@ func (_ *proposal) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *proposal) Msgsize() (s int) { - s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.Msgsize() + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.Msgsize() + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.Msgsize() + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking != nil { for zb0001, zb0002 := range (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking { _ = zb0001 @@ -5398,18 +5562,22 @@ func (z *proposal) Msgsize() (s int) { for zb0003 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { s += (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].Msgsize() } + s += 11 + msgp.ArrayHeaderSize + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + s += (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].Msgsize() + } s += 5 + (*z).unauthenticatedProposal.Block.Payset.Msgsize() + 5 + (*z).unauthenticatedProposal.SeedProof.Msgsize() + 5 + msgp.Uint64Size + 6 + (*z).unauthenticatedProposal.OriginalProposer.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *proposal) MsgIsZero() bool { - return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) + return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Proposer.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Bonus.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) } // MaxSize returns a maximum valid message size for this message type func ProposalMaxSize() (s int) { - s = 3 + 4 + basics.RoundMaxSize() + 5 + bookkeeping.BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + s = 3 + 4 + basics.RoundMaxSize() + 5 + bookkeeping.BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 4 + basics.AddressMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 s += msgp.MapHeaderSize // Adding size of map keys for z.unauthenticatedProposal.Block.BlockHeader.StateProofTracking s += protocol.NumStateProofTypes * (protocol.StateProofTypeMaxSize()) @@ -5418,6 +5586,9 @@ func ProposalMaxSize() (s int) { s += 11 // Calculating size of slice: z.unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts s += msgp.ArrayHeaderSize + ((config.MaxProposedExpiredOnlineAccounts) * (basics.AddressMaxSize())) + s += 11 + // Calculating size of slice: z.unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts + s += msgp.ArrayHeaderSize + ((config.MaxMarkAbsent) * (basics.AddressMaxSize())) s += 5 // Using maxtotalbytes for: z.unauthenticatedProposal.Block.Payset s += config.MaxTxnBytesPerBlock @@ -8856,187 +9027,229 @@ func ThresholdEventMaxSize() (s int) { func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0004Len := uint32(30) - var zb0004Mask uint64 /* 38 bits */ + zb0005Len := uint32(35) + var zb0005Mask uint64 /* 43 bits */ + if (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x80 + } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { - zb0004Len-- - zb0004Mask |= 0x80 + zb0005Len-- + zb0005Mask |= 0x100 + } + if (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x200 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x100 + zb0005Len-- + zb0005Mask |= 0x400 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0 { - zb0004Len-- - zb0004Mask |= 0x200 + zb0005Len-- + zb0005Mask |= 0x800 } if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "" { - zb0004Len-- - zb0004Mask |= 0x400 + zb0005Len-- + zb0005Mask |= 0x1000 } if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800 + zb0005Len-- + zb0005Mask |= 0x2000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000 + zb0005Len-- + zb0005Mask |= 0x4000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000 + zb0005Len-- + zb0005Mask |= 0x8000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x4000 + zb0005Len-- + zb0005Mask |= 0x10000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { - zb0004Len-- - zb0004Mask |= 0x8000 + zb0005Len-- + zb0005Mask |= 0x20000 } if (*z).unauthenticatedProposal.OriginalPeriod == 0 { - zb0004Len-- - zb0004Mask |= 0x10000 + zb0005Len-- + zb0005Mask |= 0x40000 } if (*z).unauthenticatedProposal.OriginalProposer.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x20000 + zb0005Len-- + zb0005Mask |= 0x80000 + } + if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0 { + zb0005Len-- + zb0005Mask |= 0x100000 } if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { - zb0004Len-- - zb0004Mask |= 0x40000 + zb0005Len-- + zb0005Mask |= 0x200000 + } + if (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x400000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80000 + zb0005Len-- + zb0005Mask |= 0x800000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x100000 + zb0005Len-- + zb0005Mask |= 0x1000000 + } + if (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x2000000 } if (*z).PriorVote.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200000 + zb0005Len-- + zb0005Mask |= 0x4000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0 { - zb0004Len-- - zb0004Mask |= 0x400000 + zb0005Len-- + zb0005Mask |= 0x8000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000000 + zb0005Len-- + zb0005Mask |= 0x20000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000000 + zb0005Len-- + zb0005Mask |= 0x40000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x4000000 + zb0005Len-- + zb0005Mask |= 0x80000000 } if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x8000000 + zb0005Len-- + zb0005Mask |= 0x100000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x10000000 + zb0005Len-- + zb0005Mask |= 0x200000000 } if len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0 { - zb0004Len-- - zb0004Mask |= 0x20000000 + zb0005Len-- + zb0005Mask |= 0x400000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0 { - zb0004Len-- - zb0004Mask |= 0x40000000 + zb0005Len-- + zb0005Mask |= 0x800000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0 { - zb0004Len-- - zb0004Mask |= 0x80000000 + zb0005Len-- + zb0005Mask |= 0x1000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x100000000 + zb0005Len-- + zb0005Mask |= 0x2000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200000000 + zb0005Len-- + zb0005Mask |= 0x4000000000 } if (*z).unauthenticatedProposal.Block.Payset.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400000000 + zb0005Len-- + zb0005Mask |= 0x8000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800000000 + zb0005Len-- + zb0005Mask |= 0x10000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000000000 + zb0005Len-- + zb0005Mask |= 0x20000000000 } if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false { - zb0004Len-- - zb0004Mask |= 0x2000000000 - } - // variable map header, size zb0004Len - o = msgp.AppendMapHeader(o, zb0004Len) - if zb0004Len != 0 { - if (zb0004Mask & 0x80) == 0 { // if not empty + zb0005Len-- + zb0005Mask |= 0x40000000000 + } + // variable map header, size zb0005Len + o = msgp.AppendMapHeader(o, zb0005Len) + if zb0005Len != 0 { + if (zb0005Mask & 0x80) == 0 { // if not empty + // string "bi" + o = append(o, 0xa2, 0x62, 0x69) + o = (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.MarshalMsg(o) + } + if (zb0005Mask & 0x100) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0005Mask & 0x200) == 0 { // if not empty + // string "fc" + o = append(o, 0xa2, 0x66, 0x63) + o = (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.MarshalMsg(o) + } + if (zb0005Mask & 0x400) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0005Mask & 0x800) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0005Mask & 0x1000) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0005Mask & 0x2000) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0005Mask & 0x4000) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0005Mask & 0x8000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0005Mask & 0x10000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0005Mask & 0x20000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0005Mask & 0x40000) == 0 { // if not empty // string "oper" o = append(o, 0xa4, 0x6f, 0x70, 0x65, 0x72) o = msgp.AppendUint64(o, uint64((*z).unauthenticatedProposal.OriginalPeriod)) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0005Mask & 0x80000) == 0 { // if not empty // string "oprop" o = append(o, 0xa5, 0x6f, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.OriginalProposer.MarshalMsg(o) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0005Mask & 0x100000) == 0 { // if not empty + // string "partupdabs" + o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x61, 0x62, 0x73) + if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts))) + } + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + o = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].MarshalMsg(o) + } + } + if (zb0005Mask & 0x200000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -9048,52 +9261,62 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0005Mask & 0x400000) == 0 { // if not empty + // string "pp" + o = append(o, 0xa2, 0x70, 0x70) + o = (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.MarshalMsg(o) + } + if (zb0005Mask & 0x800000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0005Mask & 0x1000000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0005Mask & 0x2000000) == 0 { // if not empty + // string "prp" + o = append(o, 0xa3, 0x70, 0x72, 0x70) + o = (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.MarshalMsg(o) + } + if (zb0005Mask & 0x4000000) == 0 { // if not empty // string "pv" o = append(o, 0xa2, 0x70, 0x76) o = (*z).PriorVote.MarshalMsg(o) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0005Mask & 0x8000000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0005Mask & 0x20000000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0005Mask & 0x40000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0005Mask & 0x80000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0005Mask & 0x100000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).unauthenticatedProposal.SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0005Mask & 0x200000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MarshalMsg(o) } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0005Mask & 0x400000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { @@ -9113,42 +9336,42 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x40000000) == 0 { // if not empty + if (zb0005Mask & 0x800000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter) } - if (zb0004Mask & 0x80000000) == 0 { // if not empty + if (zb0005Mask & 0x1000000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp) } - if (zb0004Mask & 0x100000000) == 0 { // if not empty + if (zb0005Mask & 0x2000000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x200000000) == 0 { // if not empty + if (zb0005Mask & 0x4000000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x400000000) == 0 { // if not empty + if (zb0005Mask & 0x8000000000) == 0 { // if not empty // string "txns" o = append(o, 0xa4, 0x74, 0x78, 0x6e, 0x73) o = (*z).unauthenticatedProposal.Block.Payset.MarshalMsg(o) } - if (zb0004Mask & 0x800000000) == 0 { // if not empty + if (zb0005Mask & 0x10000000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x1000000000) == 0 { // if not empty + if (zb0005Mask & 0x20000000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x2000000000) == 0 { // if not empty + if (zb0005Mask & 0x40000000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove) @@ -9171,73 +9394,73 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal st.AllowableDepth-- var field []byte _ = field - var zb0004 int - var zb0005 bool - zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Round.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Round") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Branch.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Branch") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Seed") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NativeSha512_256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Sha256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp, bts, err = msgp.ReadInt64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TimeStamp") return } } - if zb0004 > 0 { - zb0004-- - var zb0006 int - zb0006, err = msgp.ReadBytesBytesHeader(bts) + if zb0005 > 0 { + zb0005-- + var zb0007 int + zb0007, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisID") return } - if zb0006 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0006), uint64(config.MaxGenesisIDLen)) + if zb0007 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxGenesisIDLen)) return } (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -9246,157 +9469,189 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisHash") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Proposer") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "FeesCollected") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Bonus") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ProposerPayout") + return + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FeeSink") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsPool") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsLevel") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRate") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsResidue") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRecalculationRound") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CurrentProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolApprovals") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolVoteBefore") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolSwitchOn") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradePropose") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeDelay") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeApprove") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TxnCounter") return } } - if zb0004 > 0 { - zb0004-- - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0007 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0007), uint64(protocol.NumStateProofTypes)) + if zb0008 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0008), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0008 { + if zb0009 { (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0007) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0008) } - for zb0007 > 0 { + for zb0008 > 0 { var zb0001 protocol.StateProofType var zb0002 bookkeeping.StateProofTrackingData - zb0007-- + zb0008-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") @@ -9410,26 +9665,26 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } } - if zb0004 > 0 { - zb0004-- - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0009 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0010 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0010), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0010 { + if zb0011 { (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0009 { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0009] + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0010 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0010] } else { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0009) + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0010) } for zb0003 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -9439,52 +9694,81 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal } } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0012 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0013 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0012 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0012] + } else { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0012) + } + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts", zb0004) + return + } + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.Block.Payset.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Payset") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.SeedProof.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "SeedProof") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- { - var zb0011 uint64 - zb0011, bts, err = msgp.ReadUint64Bytes(bts) + var zb0014 uint64 + zb0014, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OriginalPeriod") return } - (*z).unauthenticatedProposal.OriginalPeriod = period(zb0011) + (*z).unauthenticatedProposal.OriginalPeriod = period(zb0014) } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).unauthenticatedProposal.OriginalProposer.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OriginalProposer") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).PriorVote.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "PriorVote") return } } - if zb0004 > 0 { - err = msgp.ErrTooManyArrayFields(zb0004) + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -9495,11 +9779,11 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal err = msgp.WrapError(err) return } - if zb0005 { + if zb0006 { (*z) = transmittedPayload{} } - for zb0004 > 0 { - zb0004-- + for zb0005 > 0 { + zb0005-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -9543,14 +9827,14 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal return } case "gen": - var zb0012 int - zb0012, err = msgp.ReadBytesBytesHeader(bts) + var zb0015 int + zb0015, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "GenesisID") return } - if zb0012 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxGenesisIDLen)) + if zb0015 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxGenesisIDLen)) return } (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -9564,6 +9848,30 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal err = msgp.WrapError(err, "GenesisHash") return } + case "prp": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Proposer") + return + } + case "fc": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "FeesCollected") + return + } + case "bi": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Bonus") + return + } + case "pp": + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ProposerPayout") + return + } case "fees": bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { @@ -9655,27 +9963,27 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal return } case "spt": - var zb0013 int - var zb0014 bool - zb0013, zb0014, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0016 int + var zb0017 bool + zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "StateProofTracking") return } - if zb0013 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0013), uint64(protocol.NumStateProofTypes)) + if zb0016 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0016), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "StateProofTracking") return } - if zb0014 { + if zb0017 { (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0013) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0016) } - for zb0013 > 0 { + for zb0016 > 0 { var zb0001 protocol.StateProofType var zb0002 bookkeeping.StateProofTrackingData - zb0013-- + zb0016-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "StateProofTracking") @@ -9689,24 +9997,24 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": - var zb0015 int - var zb0016 bool - zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0018 int + var zb0019 bool + zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0015 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0018 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0018), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0016 { + if zb0019 { (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0015 { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0015] + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0018 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0018] } else { - (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0015) + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0018) } for zb0003 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -9715,6 +10023,33 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal return } } + case "partupdabs": + var zb0020 int + var zb0021 bool + zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0020 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0020), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0021 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0020 { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0020] + } else { + (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0020) + } + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts", zb0004) + return + } + } case "txns": bts, err = (*z).unauthenticatedProposal.Block.Payset.UnmarshalMsgWithState(bts, st) if err != nil { @@ -9729,13 +10064,13 @@ func (z *transmittedPayload) UnmarshalMsgWithState(bts []byte, st msgp.Unmarshal } case "oper": { - var zb0017 uint64 - zb0017, bts, err = msgp.ReadUint64Bytes(bts) + var zb0022 uint64 + zb0022, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "OriginalPeriod") return } - (*z).unauthenticatedProposal.OriginalPeriod = period(zb0017) + (*z).unauthenticatedProposal.OriginalPeriod = period(zb0022) } case "oprop": bts, err = (*z).unauthenticatedProposal.OriginalProposer.UnmarshalMsgWithState(bts, st) @@ -9772,7 +10107,7 @@ func (_ *transmittedPayload) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *transmittedPayload) Msgsize() (s int) { - s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Proposer.Msgsize() + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.Msgsize() + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.Bonus.Msgsize() + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking != nil { for zb0001, zb0002 := range (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking { _ = zb0001 @@ -9784,18 +10119,22 @@ func (z *transmittedPayload) Msgsize() (s int) { for zb0003 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { s += (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].Msgsize() } + s += 11 + msgp.ArrayHeaderSize + for zb0004 := range (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + s += (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].Msgsize() + } s += 5 + (*z).unauthenticatedProposal.Block.Payset.Msgsize() + 5 + (*z).unauthenticatedProposal.SeedProof.Msgsize() + 5 + msgp.Uint64Size + 6 + (*z).unauthenticatedProposal.OriginalProposer.Msgsize() + 3 + (*z).PriorVote.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *transmittedPayload) MsgIsZero() bool { - return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) && ((*z).PriorVote.MsgIsZero()) + return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Proposer.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.FeesCollected.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Bonus.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.ProposerPayout.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) && ((*z).PriorVote.MsgIsZero()) } // MaxSize returns a maximum valid message size for this message type func TransmittedPayloadMaxSize() (s int) { - s = 3 + 4 + basics.RoundMaxSize() + 5 + bookkeeping.BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + s = 3 + 4 + basics.RoundMaxSize() + 5 + bookkeeping.BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 4 + basics.AddressMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 s += msgp.MapHeaderSize // Adding size of map keys for z.unauthenticatedProposal.Block.BlockHeader.StateProofTracking s += protocol.NumStateProofTypes * (protocol.StateProofTypeMaxSize()) @@ -9804,6 +10143,9 @@ func TransmittedPayloadMaxSize() (s int) { s += 11 // Calculating size of slice: z.unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts s += msgp.ArrayHeaderSize + ((config.MaxProposedExpiredOnlineAccounts) * (basics.AddressMaxSize())) + s += 11 + // Calculating size of slice: z.unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts + s += msgp.ArrayHeaderSize + ((config.MaxMarkAbsent) * (basics.AddressMaxSize())) s += 5 // Using maxtotalbytes for: z.unauthenticatedProposal.Block.Payset s += config.MaxTxnBytesPerBlock @@ -10516,183 +10858,225 @@ func UnauthenticatedEquivocationVoteMaxSize() (s int) { func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0004Len := uint32(29) - var zb0004Mask uint64 /* 36 bits */ + zb0005Len := uint32(34) + var zb0005Mask uint64 /* 41 bits */ + if (*z).Block.BlockHeader.Bonus.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x40 + } if (*z).Block.BlockHeader.RewardsState.RewardsLevel == 0 { - zb0004Len-- - zb0004Mask |= 0x40 + zb0005Len-- + zb0005Mask |= 0x80 + } + if (*z).Block.BlockHeader.FeesCollected.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x100 } if (*z).Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80 + zb0005Len-- + zb0005Mask |= 0x200 } if (*z).Block.BlockHeader.RewardsState.RewardsResidue == 0 { - zb0004Len-- - zb0004Mask |= 0x100 + zb0005Len-- + zb0005Mask |= 0x400 } if (*z).Block.BlockHeader.GenesisID == "" { - zb0004Len-- - zb0004Mask |= 0x200 + zb0005Len-- + zb0005Mask |= 0x800 } if (*z).Block.BlockHeader.GenesisHash.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400 + zb0005Len-- + zb0005Mask |= 0x1000 } if (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800 + zb0005Len-- + zb0005Mask |= 0x2000 } if (*z).Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000 + zb0005Len-- + zb0005Mask |= 0x4000 } if (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000 + zb0005Len-- + zb0005Mask |= 0x8000 } if (*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { - zb0004Len-- - zb0004Mask |= 0x4000 + zb0005Len-- + zb0005Mask |= 0x10000 } if (*z).OriginalPeriod == 0 { - zb0004Len-- - zb0004Mask |= 0x8000 + zb0005Len-- + zb0005Mask |= 0x20000 } if (*z).OriginalProposer.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x10000 + zb0005Len-- + zb0005Mask |= 0x40000 + } + if len((*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0 { + zb0005Len-- + zb0005Mask |= 0x80000 } if len((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { - zb0004Len-- - zb0004Mask |= 0x20000 + zb0005Len-- + zb0005Mask |= 0x100000 + } + if (*z).Block.BlockHeader.ProposerPayout.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x200000 } if (*z).Block.BlockHeader.Branch.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40000 + zb0005Len-- + zb0005Mask |= 0x400000 } if (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80000 + zb0005Len-- + zb0005Mask |= 0x800000 + } + if (*z).Block.BlockHeader.Proposer.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x1000000 } if (*z).Block.BlockHeader.RewardsState.RewardsRate == 0 { - zb0004Len-- - zb0004Mask |= 0x100000 + zb0005Len-- + zb0005Mask |= 0x2000000 } if (*z).Block.BlockHeader.Round.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400000 + zb0005Len-- + zb0005Mask |= 0x8000000 } if (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800000 + zb0005Len-- + zb0005Mask |= 0x10000000 } if (*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000000 + zb0005Len-- + zb0005Mask |= 0x20000000 } if (*z).SeedProof.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000000 + zb0005Len-- + zb0005Mask |= 0x40000000 } if (*z).Block.BlockHeader.Seed.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x4000000 + zb0005Len-- + zb0005Mask |= 0x80000000 } if len((*z).Block.BlockHeader.StateProofTracking) == 0 { - zb0004Len-- - zb0004Mask |= 0x8000000 + zb0005Len-- + zb0005Mask |= 0x100000000 } if (*z).Block.BlockHeader.TxnCounter == 0 { - zb0004Len-- - zb0004Mask |= 0x10000000 + zb0005Len-- + zb0005Mask |= 0x200000000 } if (*z).Block.BlockHeader.TimeStamp == 0 { - zb0004Len-- - zb0004Mask |= 0x20000000 + zb0005Len-- + zb0005Mask |= 0x400000000 } if (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40000000 + zb0005Len-- + zb0005Mask |= 0x800000000 } if (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80000000 + zb0005Len-- + zb0005Mask |= 0x1000000000 } if (*z).Block.Payset.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x100000000 + zb0005Len-- + zb0005Mask |= 0x2000000000 } if (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200000000 + zb0005Len-- + zb0005Mask |= 0x4000000000 } if (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400000000 + zb0005Len-- + zb0005Mask |= 0x8000000000 } if (*z).Block.BlockHeader.UpgradeVote.UpgradeApprove == false { - zb0004Len-- - zb0004Mask |= 0x800000000 - } - // variable map header, size zb0004Len - o = msgp.AppendMapHeader(o, zb0004Len) - if zb0004Len != 0 { - if (zb0004Mask & 0x40) == 0 { // if not empty + zb0005Len-- + zb0005Mask |= 0x10000000000 + } + // variable map header, size zb0005Len + o = msgp.AppendMapHeader(o, zb0005Len) + if zb0005Len != 0 { + if (zb0005Mask & 0x40) == 0 { // if not empty + // string "bi" + o = append(o, 0xa2, 0x62, 0x69) + o = (*z).Block.BlockHeader.Bonus.MarshalMsg(o) + } + if (zb0005Mask & 0x80) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x80) == 0 { // if not empty + if (zb0005Mask & 0x100) == 0 { // if not empty + // string "fc" + o = append(o, 0xa2, 0x66, 0x63) + o = (*z).Block.BlockHeader.FeesCollected.MarshalMsg(o) + } + if (zb0005Mask & 0x200) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).Block.BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0005Mask & 0x400) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0005Mask & 0x800) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).Block.BlockHeader.GenesisID) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0005Mask & 0x1000) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).Block.BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0005Mask & 0x2000) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0005Mask & 0x4000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).Block.BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0005Mask & 0x8000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0005Mask & 0x10000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0005Mask & 0x20000) == 0 { // if not empty // string "oper" o = append(o, 0xa4, 0x6f, 0x70, 0x65, 0x72) o = msgp.AppendUint64(o, uint64((*z).OriginalPeriod)) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0005Mask & 0x40000) == 0 { // if not empty // string "oprop" o = append(o, 0xa5, 0x6f, 0x70, 0x72, 0x6f, 0x70) o = (*z).OriginalProposer.MarshalMsg(o) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0005Mask & 0x80000) == 0 { // if not empty + // string "partupdabs" + o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x61, 0x62, 0x73) + if (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts))) + } + for zb0004 := range (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + o = (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].MarshalMsg(o) + } + } + if (zb0005Mask & 0x100000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -10704,47 +11088,57 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0005Mask & 0x200000) == 0 { // if not empty + // string "pp" + o = append(o, 0xa2, 0x70, 0x70) + o = (*z).Block.BlockHeader.ProposerPayout.MarshalMsg(o) + } + if (zb0005Mask & 0x400000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).Block.BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0005Mask & 0x800000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0005Mask & 0x1000000) == 0 { // if not empty + // string "prp" + o = append(o, 0xa3, 0x70, 0x72, 0x70) + o = (*z).Block.BlockHeader.Proposer.MarshalMsg(o) + } + if (zb0005Mask & 0x2000000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0005Mask & 0x8000000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0005Mask & 0x10000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0005Mask & 0x20000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0005Mask & 0x40000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0005Mask & 0x80000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).Block.BlockHeader.Seed.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0005Mask & 0x100000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).Block.BlockHeader.StateProofTracking == nil { @@ -10764,42 +11158,42 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0005Mask & 0x200000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.TxnCounter) } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0005Mask & 0x400000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).Block.BlockHeader.TimeStamp) } - if (zb0004Mask & 0x40000000) == 0 { // if not empty + if (zb0005Mask & 0x800000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x80000000) == 0 { // if not empty + if (zb0005Mask & 0x1000000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x100000000) == 0 { // if not empty + if (zb0005Mask & 0x2000000000) == 0 { // if not empty // string "txns" o = append(o, 0xa4, 0x74, 0x78, 0x6e, 0x73) o = (*z).Block.Payset.MarshalMsg(o) } - if (zb0004Mask & 0x200000000) == 0 { // if not empty + if (zb0005Mask & 0x4000000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x400000000) == 0 { // if not empty + if (zb0005Mask & 0x8000000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x800000000) == 0 { // if not empty + if (zb0005Mask & 0x10000000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).Block.BlockHeader.UpgradeVote.UpgradeApprove) @@ -10822,73 +11216,73 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma st.AllowableDepth-- var field []byte _ = field - var zb0004 int - var zb0005 bool - zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.Round.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Round") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.Branch.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Branch") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.Seed.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Seed") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NativeSha512_256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Sha256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).Block.BlockHeader.TimeStamp, bts, err = msgp.ReadInt64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TimeStamp") return } } - if zb0004 > 0 { - zb0004-- - var zb0006 int - zb0006, err = msgp.ReadBytesBytesHeader(bts) + if zb0005 > 0 { + zb0005-- + var zb0007 int + zb0007, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisID") return } - if zb0006 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0006), uint64(config.MaxGenesisIDLen)) + if zb0007 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxGenesisIDLen)) return } (*z).Block.BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -10897,157 +11291,189 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.GenesisHash.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisHash") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + bts, err = (*z).Block.BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Proposer") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).Block.BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "FeesCollected") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).Block.BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Bonus") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).Block.BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ProposerPayout") + return + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FeeSink") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.RewardsState.RewardsPool.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsPool") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).Block.BlockHeader.RewardsState.RewardsLevel, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsLevel") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).Block.BlockHeader.RewardsState.RewardsRate, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRate") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).Block.BlockHeader.RewardsState.RewardsResidue, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsResidue") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRecalculationRound") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CurrentProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.UpgradeState.NextProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolApprovals") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolVoteBefore") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolSwitchOn") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradePropose") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeDelay") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).Block.BlockHeader.UpgradeVote.UpgradeApprove, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeApprove") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).Block.BlockHeader.TxnCounter, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TxnCounter") return } } - if zb0004 > 0 { - zb0004-- - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0007 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0007), uint64(protocol.NumStateProofTypes)) + if zb0008 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0008), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0008 { + if zb0009 { (*z).Block.BlockHeader.StateProofTracking = nil } else if (*z).Block.BlockHeader.StateProofTracking == nil { - (*z).Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0007) + (*z).Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0008) } - for zb0007 > 0 { + for zb0008 > 0 { var zb0001 protocol.StateProofType var zb0002 bookkeeping.StateProofTrackingData - zb0007-- + zb0008-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") @@ -11061,26 +11487,26 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma (*z).Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } } - if zb0004 > 0 { - zb0004-- - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0009 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0010 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0010), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0010 { + if zb0011 { (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0009 { - (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0009] + } else if (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0010 { + (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0010] } else { - (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0009) + (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0010) } for zb0003 := range (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -11090,44 +11516,73 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma } } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0012 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0013 { + (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0012 { + (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0012] + } else { + (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0012) + } + for zb0004 := range (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts", zb0004) + return + } + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).Block.Payset.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Payset") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).SeedProof.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "SeedProof") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- { - var zb0011 uint64 - zb0011, bts, err = msgp.ReadUint64Bytes(bts) + var zb0014 uint64 + zb0014, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OriginalPeriod") return } - (*z).OriginalPeriod = period(zb0011) + (*z).OriginalPeriod = period(zb0014) } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).OriginalProposer.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "OriginalProposer") return } } - if zb0004 > 0 { - err = msgp.ErrTooManyArrayFields(zb0004) + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -11138,11 +11593,11 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma err = msgp.WrapError(err) return } - if zb0005 { + if zb0006 { (*z) = unauthenticatedProposal{} } - for zb0004 > 0 { - zb0004-- + for zb0005 > 0 { + zb0005-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -11186,14 +11641,14 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma return } case "gen": - var zb0012 int - zb0012, err = msgp.ReadBytesBytesHeader(bts) + var zb0015 int + zb0015, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "GenesisID") return } - if zb0012 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxGenesisIDLen)) + if zb0015 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxGenesisIDLen)) return } (*z).Block.BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -11207,6 +11662,30 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma err = msgp.WrapError(err, "GenesisHash") return } + case "prp": + bts, err = (*z).Block.BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Proposer") + return + } + case "fc": + bts, err = (*z).Block.BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "FeesCollected") + return + } + case "bi": + bts, err = (*z).Block.BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Bonus") + return + } + case "pp": + bts, err = (*z).Block.BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ProposerPayout") + return + } case "fees": bts, err = (*z).Block.BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { @@ -11298,27 +11777,27 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma return } case "spt": - var zb0013 int - var zb0014 bool - zb0013, zb0014, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0016 int + var zb0017 bool + zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "StateProofTracking") return } - if zb0013 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0013), uint64(protocol.NumStateProofTypes)) + if zb0016 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0016), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "StateProofTracking") return } - if zb0014 { + if zb0017 { (*z).Block.BlockHeader.StateProofTracking = nil } else if (*z).Block.BlockHeader.StateProofTracking == nil { - (*z).Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0013) + (*z).Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0016) } - for zb0013 > 0 { + for zb0016 > 0 { var zb0001 protocol.StateProofType var zb0002 bookkeeping.StateProofTrackingData - zb0013-- + zb0016-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "StateProofTracking") @@ -11332,24 +11811,24 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma (*z).Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": - var zb0015 int - var zb0016 bool - zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0018 int + var zb0019 bool + zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0015 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0018 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0018), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0016 { + if zb0019 { (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0015 { - (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0015] + } else if (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0018 { + (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0018] } else { - (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0015) + (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0018) } for zb0003 := range (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -11358,6 +11837,33 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma return } } + case "partupdabs": + var zb0020 int + var zb0021 bool + zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0020 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0020), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0021 { + (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0020 { + (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0020] + } else { + (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0020) + } + for zb0004 := range (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts", zb0004) + return + } + } case "txns": bts, err = (*z).Block.Payset.UnmarshalMsgWithState(bts, st) if err != nil { @@ -11372,13 +11878,13 @@ func (z *unauthenticatedProposal) UnmarshalMsgWithState(bts []byte, st msgp.Unma } case "oper": { - var zb0017 uint64 - zb0017, bts, err = msgp.ReadUint64Bytes(bts) + var zb0022 uint64 + zb0022, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "OriginalPeriod") return } - (*z).OriginalPeriod = period(zb0017) + (*z).OriginalPeriod = period(zb0022) } case "oprop": bts, err = (*z).OriginalProposer.UnmarshalMsgWithState(bts, st) @@ -11409,7 +11915,7 @@ func (_ *unauthenticatedProposal) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *unauthenticatedProposal) Msgsize() (s int) { - s = 3 + 4 + (*z).Block.BlockHeader.Round.Msgsize() + 5 + (*z).Block.BlockHeader.Branch.Msgsize() + 5 + (*z).Block.BlockHeader.Seed.Msgsize() + 4 + (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).Block.BlockHeader.GenesisID) + 3 + (*z).Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + s = 3 + 4 + (*z).Block.BlockHeader.Round.Msgsize() + 5 + (*z).Block.BlockHeader.Branch.Msgsize() + 5 + (*z).Block.BlockHeader.Seed.Msgsize() + 4 + (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).Block.BlockHeader.GenesisID) + 3 + (*z).Block.BlockHeader.GenesisHash.Msgsize() + 4 + (*z).Block.BlockHeader.Proposer.Msgsize() + 3 + (*z).Block.BlockHeader.FeesCollected.Msgsize() + 3 + (*z).Block.BlockHeader.Bonus.Msgsize() + 3 + (*z).Block.BlockHeader.ProposerPayout.Msgsize() + 5 + (*z).Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize if (*z).Block.BlockHeader.StateProofTracking != nil { for zb0001, zb0002 := range (*z).Block.BlockHeader.StateProofTracking { _ = zb0001 @@ -11421,18 +11927,22 @@ func (z *unauthenticatedProposal) Msgsize() (s int) { for zb0003 := range (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { s += (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].Msgsize() } + s += 11 + msgp.ArrayHeaderSize + for zb0004 := range (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + s += (*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].Msgsize() + } s += 5 + (*z).Block.Payset.Msgsize() + 5 + (*z).SeedProof.Msgsize() + 5 + msgp.Uint64Size + 6 + (*z).OriginalProposer.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *unauthenticatedProposal) MsgIsZero() bool { - return ((*z).Block.BlockHeader.Round.MsgIsZero()) && ((*z).Block.BlockHeader.Branch.MsgIsZero()) && ((*z).Block.BlockHeader.Seed.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TimeStamp == 0) && ((*z).Block.BlockHeader.GenesisID == "") && ((*z).Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).Block.BlockHeader.TxnCounter == 0) && (len((*z).Block.BlockHeader.StateProofTracking) == 0) && (len((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).Block.Payset.MsgIsZero()) && ((*z).SeedProof.MsgIsZero()) && ((*z).OriginalPeriod == 0) && ((*z).OriginalProposer.MsgIsZero()) + return ((*z).Block.BlockHeader.Round.MsgIsZero()) && ((*z).Block.BlockHeader.Branch.MsgIsZero()) && ((*z).Block.BlockHeader.Seed.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TimeStamp == 0) && ((*z).Block.BlockHeader.GenesisID == "") && ((*z).Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).Block.BlockHeader.Proposer.MsgIsZero()) && ((*z).Block.BlockHeader.FeesCollected.MsgIsZero()) && ((*z).Block.BlockHeader.Bonus.MsgIsZero()) && ((*z).Block.BlockHeader.ProposerPayout.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).Block.BlockHeader.TxnCounter == 0) && (len((*z).Block.BlockHeader.StateProofTracking) == 0) && (len((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && (len((*z).Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0) && ((*z).Block.Payset.MsgIsZero()) && ((*z).SeedProof.MsgIsZero()) && ((*z).OriginalPeriod == 0) && ((*z).OriginalProposer.MsgIsZero()) } // MaxSize returns a maximum valid message size for this message type func UnauthenticatedProposalMaxSize() (s int) { - s = 3 + 4 + basics.RoundMaxSize() + 5 + bookkeeping.BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + s = 3 + 4 + basics.RoundMaxSize() + 5 + bookkeeping.BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 4 + basics.AddressMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 s += msgp.MapHeaderSize // Adding size of map keys for z.Block.BlockHeader.StateProofTracking s += protocol.NumStateProofTypes * (protocol.StateProofTypeMaxSize()) @@ -11441,6 +11951,9 @@ func UnauthenticatedProposalMaxSize() (s int) { s += 11 // Calculating size of slice: z.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts s += msgp.ArrayHeaderSize + ((config.MaxProposedExpiredOnlineAccounts) * (basics.AddressMaxSize())) + s += 11 + // Calculating size of slice: z.Block.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts + s += msgp.ArrayHeaderSize + ((config.MaxMarkAbsent) * (basics.AddressMaxSize())) s += 5 // Using maxtotalbytes for: z.Block.Payset s += config.MaxTxnBytesPerBlock diff --git a/agreement/player_permutation_test.go b/agreement/player_permutation_test.go index e41195d1f1..216316d8bd 100644 --- a/agreement/player_permutation_test.go +++ b/agreement/player_permutation_test.go @@ -25,19 +25,22 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) func makeRandomProposalPayload(r round) *proposal { f := testBlockFactory{Owner: 1} - ve, _ := f.AssembleBlock(r) + ub, _ := f.AssembleBlock(r, nil) + pb := ub.FinishBlock(committee.Seed{}, basics.Address{}, false) var payload unauthenticatedProposal - payload.Block = ve.Block() + payload.Block = bookkeeping.Block(pb) payload.SeedProof = crypto.VRFProof{} - return &proposal{unauthenticatedProposal: payload, ve: ve} + return &proposal{unauthenticatedProposal: payload} } var errTestVerifyFailed = makeSerErrStr("test error") diff --git a/agreement/proposal.go b/agreement/proposal.go index ac29970b16..e696bfeb4b 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -21,6 +21,7 @@ import ( "fmt" "time" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" @@ -101,14 +102,24 @@ type proposal struct { validatedAt time.Duration } -func makeProposal(ve ValidatedBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { +func makeProposalFromProposableBlock(blk Block, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { + e := bookkeeping.Block(blk) + var payload unauthenticatedProposal + payload.Block = e + payload.SeedProof = pf + payload.OriginalPeriod = origPer + payload.OriginalProposer = origProp + return proposal{unauthenticatedProposal: payload} // ve set to nil -- won't cache deltas +} + +func makeProposalFromValidatedBlock(ve ValidatedBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { e := ve.Block() var payload unauthenticatedProposal payload.Block = e payload.SeedProof = pf payload.OriginalPeriod = origPer payload.OriginalProposer = origProp - return proposal{unauthenticatedProposal: payload, ve: ve} + return proposal{unauthenticatedProposal: payload, ve: ve} // store ve to use when calling Ledger.EnsureValidatedBlock } func (p proposal) u() unauthenticatedProposal { @@ -141,15 +152,10 @@ func (i seedInput) ToBeHashed() (protocol.HashID, []byte) { return protocol.ProposerSeed, protocol.Encode(&i) } -func deriveNewSeed(address basics.Address, vrf *crypto.VRFSecrets, rnd round, period period, ledger LedgerReader) (newSeed committee.Seed, seedProof crypto.VRFProof, reterr error) { +func deriveNewSeed(address basics.Address, vrf *crypto.VRFSecrets, rnd round, period period, ledger LedgerReader, cparams config.ConsensusParams) (newSeed committee.Seed, seedProof crypto.VRFProof, reterr error) { var ok bool var vrfOut crypto.VrfOutput - cparams, err := ledger.ConsensusParams(ParamsRound(rnd)) - if err != nil { - reterr = fmt.Errorf("failed to obtain consensus parameters in round %d: %v", ParamsRound(rnd), err) - return - } var alpha crypto.Digest prevSeed, err := ledger.Seed(seedRound(rnd, cparams)) if err != nil { @@ -189,31 +195,52 @@ func deriveNewSeed(address basics.Address, vrf *crypto.VRFSecrets, rnd round, pe return } -func verifyNewSeed(p unauthenticatedProposal, ledger LedgerReader) error { +// verifyProposer checks the things in the header that can only be confirmed by +// looking into the unauthenticatedProposal or using LookupAgreement. The +// Proposer, ProposerPayout, and Seed. +func verifyProposer(p unauthenticatedProposal, ledger LedgerReader) error { value := p.value() rnd := p.Round() + + // ledger.ConsensusParams(rnd) is not allowed because rnd isn't committed. + // The BlockHeader isn't trustworthy yet, since we haven't checked the + // upgrade state. So, lacking the current consensus params, we confirm that + // the Proposer is *either* correct or missing. `eval` package will using + // Payouts.Enabled to confirm which it should be. + if !p.Proposer().IsZero() && p.Proposer() != value.OriginalProposer { + return fmt.Errorf("wrong proposer (%v != %v)", p.Proposer(), value.OriginalProposer) + } + cparams, err := ledger.ConsensusParams(ParamsRound(rnd)) if err != nil { - return fmt.Errorf("failed to obtain consensus parameters in round %d: %v", ParamsRound(rnd), err) + return fmt.Errorf("failed to obtain consensus parameters in round %d: %w", ParamsRound(rnd), err) } - balanceRound := balanceRound(rnd, cparams) - proposerRecord, err := ledger.LookupAgreement(balanceRound, value.OriginalProposer) + // Similarly, we only check here that the payout is zero if + // ineligible. `eval` code must check that it is correct if > 0. We pass + // OriginalProposer instead of p.Proposer so that the call returns the + // proper record, even before Payouts.Enabled (it will be used below to + // check the Seed). + eligible, proposerRecord, err := payoutEligible(rnd, value.OriginalProposer, ledger, cparams) if err != nil { - return fmt.Errorf("failed to obtain balance record for address %v in round %d: %v", value.OriginalProposer, balanceRound, err) + return fmt.Errorf("failed to determine incentive eligibility %w", err) + } + if !eligible && p.ProposerPayout().Raw > 0 { + return fmt.Errorf("proposer payout (%d) for ineligible Proposer %v", + p.ProposerPayout().Raw, p.Proposer()) } var alpha crypto.Digest prevSeed, err := ledger.Seed(seedRound(rnd, cparams)) if err != nil { - return fmt.Errorf("failed read seed of round %d: %v", seedRound(rnd, cparams), err) + return fmt.Errorf("failed to read seed of round %d: %v", seedRound(rnd, cparams), err) } if value.OriginalPeriod == 0 { verifier := proposerRecord.SelectionID ok, _ := verifier.Verify(p.SeedProof, prevSeed) // ignoring VrfOutput returned by Verify if !ok { - return fmt.Errorf("payload seed proof malformed (%v, %v)", prevSeed, p.SeedProof) + return fmt.Errorf("seed proof malformed (%v, %v)", prevSeed, p.SeedProof) } // TODO remove the following Hash() call, // redundant with the Verify() call above. @@ -239,27 +266,63 @@ func verifyNewSeed(p unauthenticatedProposal, ledger LedgerReader) error { input.History = oldDigest } if p.Seed() != committee.Seed(crypto.HashObj(input)) { - return fmt.Errorf("payload seed malformed (%v != %v)", committee.Seed(crypto.HashObj(input)), p.Seed()) + return fmt.Errorf("seed malformed (%v != %v)", committee.Seed(crypto.HashObj(input)), p.Seed()) } return nil } -func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, ve ValidatedBlock, period period, ledger LedgerReader) (proposal, proposalValue, error) { - rnd := ve.Block().Round() - newSeed, seedProof, err := deriveNewSeed(address, vrf, rnd, period, ledger) +// payoutEligible determines whether the proposer is eligible for block +// incentive payout. It will return false before payouts begin since no record +// will be IncentiveEligible. But, since we feed the true proposer in even if +// the header lacks it, the returned balanceRecord will be the right record. +func payoutEligible(rnd basics.Round, proposer basics.Address, ledger LedgerReader, cparams config.ConsensusParams) (bool, basics.OnlineAccountData, error) { + // Check the balance from the agreement round + balanceRound := balanceRound(rnd, cparams) + balanceRecord, err := ledger.LookupAgreement(balanceRound, proposer) if err != nil { - return proposal{}, proposalValue{}, fmt.Errorf("proposalForBlock: could not derive new seed: %v", err) + return false, basics.OnlineAccountData{}, err } - ve = ve.WithSeed(newSeed) - proposal := makeProposal(ve, seedProof, period, address) + // When payouts begin, nobody could possible have IncentiveEligible set in + // the balanceRound, so the min/max check is irrelevant. + balanceParams, err := ledger.ConsensusParams(balanceRound) + if err != nil { + return false, basics.OnlineAccountData{}, err + } + eligible := balanceRecord.IncentiveEligible && + balanceRecord.MicroAlgosWithRewards.Raw >= balanceParams.Payouts.MinBalance && + balanceRecord.MicroAlgosWithRewards.Raw <= balanceParams.Payouts.MaxBalance + return eligible, balanceRecord, nil +} + +func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk UnfinishedBlock, period period, ledger LedgerReader) (proposal, proposalValue, error) { + rnd := blk.Round() + + cparams, err := ledger.ConsensusParams(ParamsRound(rnd)) + if err != nil { + return proposal{}, proposalValue{}, fmt.Errorf("proposalForBlock: no consensus parameters for round %d: %w", ParamsRound(rnd), err) + } + + newSeed, seedProof, err := deriveNewSeed(address, vrf, rnd, period, ledger, cparams) + if err != nil { + return proposal{}, proposalValue{}, fmt.Errorf("proposalForBlock: could not derive new seed: %w", err) + } + + eligible, _, err := payoutEligible(rnd, address, ledger, cparams) + if err != nil { + return proposal{}, proposalValue{}, fmt.Errorf("proposalForBlock: could determine eligibility: %w", err) + } + + proposableBlock := blk.FinishBlock(newSeed, address, eligible) + prop := makeProposalFromProposableBlock(proposableBlock, seedProof, period, address) + value := proposalValue{ OriginalPeriod: period, OriginalProposer: address, - BlockDigest: proposal.Block.Digest(), - EncodingDigest: crypto.HashObj(proposal), + BlockDigest: prop.Block.Digest(), + EncodingDigest: crypto.HashObj(prop), } - return proposal, value, nil + return prop, value, nil } // validate returns true if the proposal is valid. @@ -272,15 +335,15 @@ func (p unauthenticatedProposal) validate(ctx context.Context, current round, le return invalid, fmt.Errorf("proposed entry from wrong round: entry.Round() != current: %v != %v", entry.Round(), current) } - err := verifyNewSeed(p, ledger) + err := verifyProposer(p, ledger) if err != nil { - return invalid, fmt.Errorf("proposal has bad seed: %v", err) + return invalid, fmt.Errorf("unable to verify header: %w", err) } ve, err := validator.Validate(ctx, entry) if err != nil { - return invalid, fmt.Errorf("EntryValidator rejected entry: %v", err) + return invalid, fmt.Errorf("EntryValidator rejected entry: %w", err) } - return makeProposal(ve, p.SeedProof, p.OriginalPeriod, p.OriginalProposer), nil + return makeProposalFromValidatedBlock(ve, p.SeedProof, p.OriginalPeriod, p.OriginalProposer), nil } diff --git a/agreement/proposalStore_test.go b/agreement/proposalStore_test.go index 2ce8c23753..d434a3cca8 100644 --- a/agreement/proposalStore_test.go +++ b/agreement/proposalStore_test.go @@ -64,7 +64,7 @@ func TestBlockAssemblerPipeline(t *testing.T) { round := player.Round period := player.Period - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", round, err) accountIndex := 0 @@ -132,7 +132,7 @@ func TestBlockAssemblerBind(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 @@ -200,7 +200,7 @@ func TestBlockAssemblerAuthenticator(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, _, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -266,7 +266,7 @@ func TestBlockAssemblerTrim(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, _, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -339,7 +339,7 @@ func TestProposalStoreT(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, proposalV, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -413,7 +413,7 @@ func TestProposalStoreUnderlying(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, proposalV, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -477,7 +477,7 @@ func TestProposalStoreHandle(t *testing.T) { proposalVoteEventBatch, proposalPayloadEventBatch, _ := generateProposalEvents(t, player, accounts, factory, ledger) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 _, proposalV0, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -661,7 +661,7 @@ func TestProposalStoreGetPinnedValue(t *testing.T) { // create proposal Store player, router, accounts, factory, ledger := testPlayerSetup() - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 // create a route handler for the proposal store diff --git a/agreement/proposal_test.go b/agreement/proposal_test.go index 98cb177073..f325c507d6 100644 --- a/agreement/proposal_test.go +++ b/agreement/proposal_test.go @@ -46,7 +46,7 @@ func testSetup(periodCount uint64) (player, rootRouter, testAccountData, testBlo } func createProposalsTesting(accs testAccountData, round basics.Round, period period, factory BlockFactory, ledger Ledger) (ps []proposal, vs []vote) { - ve, err := factory.AssembleBlock(round) + ve, err := factory.AssembleBlock(round, accs.addresses) if err != nil { logging.Base().Errorf("Could not generate a proposal for round %d: %v", round, err) return nil, nil @@ -122,7 +122,7 @@ func TestProposalFunctions(t *testing.T) { player, _, accs, factory, ledger := testSetup(0) round := player.Round period := player.Period - ve, err := factory.AssembleBlock(player.Round) + ve, err := factory.AssembleBlock(player.Round, accs.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", round, err) validator := testBlockValidator{} @@ -162,7 +162,7 @@ func TestProposalUnauthenticated(t *testing.T) { round := player.Round period := player.Period - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", round, err) validator := testBlockValidator{} @@ -201,6 +201,15 @@ func TestProposalUnauthenticated(t *testing.T) { unauthenticatedProposal3.SeedProof = unauthenticatedProposal.SeedProof _, err = unauthenticatedProposal3.validate(context.Background(), round, ledger, validator) require.Error(t, err) + + // validate mismatch proposer address between block and unauthenticatedProposal + proposal4, _, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, period, ledger) + accountIndex++ + unauthenticatedProposal4 := proposal4.u() + unauthenticatedProposal4.OriginalProposer = accounts.addresses[0] // set to the wrong address + require.NotEqual(t, unauthenticatedProposal4.OriginalProposer, unauthenticatedProposal4.Block.Proposer()) + _, err = unauthenticatedProposal4.validate(context.Background(), round, ledger, validator) + require.ErrorContains(t, err, "wrong proposer") } func unauthenticatedProposalBlockPanicWrapper(t *testing.T, message string, uap unauthenticatedProposal, validator BlockValidator) (block bookkeeping.Block) { diff --git a/agreement/pseudonode.go b/agreement/pseudonode.go index e0bfb326bd..fe5423c025 100644 --- a/agreement/pseudonode.go +++ b/agreement/pseudonode.go @@ -284,7 +284,11 @@ func (n asyncPseudonode) makePseudonodeVerifier(voteVerifier *AsyncVoteVerifier) // makeProposals creates a slice of block proposals for the given round and period. func (n asyncPseudonode) makeProposals(round basics.Round, period period, accounts []account.ParticipationRecordForRound) ([]proposal, []unauthenticatedVote) { - ve, err := n.factory.AssembleBlock(round) + addresses := make([]basics.Address, len(accounts)) + for i := range accounts { + addresses[i] = accounts[i].Account + } + ve, err := n.factory.AssembleBlock(round, addresses) if err != nil { if err != ErrAssembleBlockRoundStale { n.log.Errorf("pseudonode.makeProposals: could not generate a proposal for round %d: %v", round, err) diff --git a/agreement/vote.go b/agreement/vote.go index 5dad87c85c..0d0c5da27f 100644 --- a/agreement/vote.go +++ b/agreement/vote.go @@ -148,7 +148,7 @@ func (uv unauthenticatedVote) verify(l LedgerReader) (vote, error) { // makeVote creates a new unauthenticated vote from its constituent components. // -// makeVote returns an error it it fails. +// makeVote returns an error if it fails. func makeVote(rv rawVote, voting crypto.OneTimeSigner, selection *crypto.VRFSecrets, l Ledger) (unauthenticatedVote, error) { m, err := membership(l, rv.Sender, rv.Round, rv.Period, rv.Step) if err != nil { diff --git a/config/consensus.go b/config/consensus.go index fc08e2990e..f86e45e831 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -447,8 +447,8 @@ type ConsensusParams struct { EnableExtraPagesOnAppUpdate bool - // MaxProposedExpiredOnlineAccounts is the maximum number of online accounts, which need - // to be taken offline, that would be proposed to be taken offline. + // MaxProposedExpiredOnlineAccounts is the maximum number of online accounts + // that a proposer can take offline for having expired voting keys. MaxProposedExpiredOnlineAccounts int // EnableAccountDataResourceSeparation enables the support for extended application and asset storage @@ -529,6 +529,101 @@ type ConsensusParams struct { // arrival times or is set to a static value. Even if this flag disables the // dynamic filter, it will be calculated and logged (but not used). DynamicFilterTimeout bool + + // Payouts contains parameters for amounts and eligibility for block proposer + // payouts. It excludes information about the "unsustainable" payouts + // described in BonusPlan. + Payouts ProposerPayoutRules + + // Bonus contains parameters related to the extra payout made to block + // proposers, unrelated to the fees paid in that block. For it to actually + // occur, extra funds need to be put into the FeeSink. The bonus amount + // decays exponentially. + Bonus BonusPlan +} + +// ProposerPayoutRules puts several related consensus parameters in one place. The same +// care for backward compatibility with old blocks must be taken. +type ProposerPayoutRules struct { + // Enabled turns on several things needed for paying block incentives, + // including tracking of the proposer and fees collected. + Enabled bool + + // GoOnlineFee imparts a small cost on moving from offline to online. This + // will impose a cost to running unreliable nodes that get suspended and + // then come back online. + GoOnlineFee uint64 + + // Percent specifies the percent of fees paid in a block that go to the + // proposer instead of the FeeSink. + Percent uint64 + + // MinBalance is the minimum balance an account must have to be eligible for + // incentives. It ensures that smaller accounts continue to operate for the + // same motivations they had before block incentives were + // introduced. Without that assurance, it is difficult to model their + // behaviour - might many participants join for the hope of easy financial + // rewards, but without caring enough to run a high-quality node? + MinBalance uint64 + + // MaxBalance is the maximum balance an account can have to be eligible for + // incentives. It encourages large accounts to split their stake to add + // resilience to consensus in the case of outages. Nothing in protocol can + // prevent such accounts from running nodes that share fate (same machine, + // same data center, etc), but this serves as a gentle reminder. + MaxBalance uint64 + + // MaxMarkAbsent is the maximum number of online accounts, that a proposer + // can suspend for not proposing "lately" (In 10x expected interval, or + // within a grace period from being challenged) + MaxMarkAbsent int + + // Challenges occur once every challengeInterval rounds. + ChallengeInterval uint64 + // Suspensions happen between 1 and 2 grace periods after a challenge. Must + // be less than half MaxTxnLife to ensure the Block header will be cached + // and less than half ChallengeInterval to avoid overlapping challenges. A larger + // grace period means larger stake nodes will probably propose before they + // need to consider an active heartbeat. + ChallengeGracePeriod uint64 + // An account is challenged if the first challengeBits match the start of + // the account address. An online account will be challenged about once + // every interval*2^bits rounds. + ChallengeBits int +} + +// BonusPlan describes how the "extra" proposer payouts are to be made. It +// specifies an exponential decay in which the bonus decreases by 1% every n +// rounds. If we need to change the decay rate (only), we would create a new +// plan like: +// +// BaseAmount: 0, DecayInterval: XXX +// +// by using a zero baseAmount, the amount not affected. +// For a bigger change, we'd use a plan like: +// +// BaseRound: , BaseAmount: , DecayInterval: +// +// or just +// +// BaseAmount: , DecayInterval: +// +// the new decay rate would go into effect at upgrade time, and the new +// amount would be set at baseRound or at upgrade time. +type BonusPlan struct { + // BaseRound is the earliest round this plan can apply. Of course, the + // consensus update must also have happened. So using a low value makes it + // go into effect immediately upon upgrade. + BaseRound uint64 + // BaseAmount is the bonus to be paid when this plan first applies (see + // baseRound). If it is zero, then no explicit change is made to the bonus + // (useful for only changing the decay rate). + BaseAmount uint64 + // DecayInterval is the time in rounds between 1% decays. For simplicity, + // decay occurs based on round % BonusDecayInterval, so a decay can happen right + // after going into effect. The BonusDecayInterval goes into effect at upgrade + // time, regardless of `baseRound`. + DecayInterval uint64 } // PaysetCommitType enumerates possible ways for the block header to commit to @@ -603,10 +698,14 @@ var MaxExtraAppProgramLen int // supported supported by any of the consensus protocols. used for decoding purposes. var MaxAvailableAppProgramLen int -// MaxProposedExpiredOnlineAccounts is the maximum number of online accounts, which need -// to be taken offline, that would be proposed to be taken offline. +// MaxProposedExpiredOnlineAccounts is the maximum number of online accounts +// that a proposer can take offline for having expired voting keys. var MaxProposedExpiredOnlineAccounts int +// MaxMarkAbsent is the maximum number of online accounts that a proposer can +// suspend for not proposing "lately" +var MaxMarkAbsent int + // MaxAppTotalArgLen is the maximum number of bytes across all arguments of an application // max sum([len(arg) for arg in txn.ApplicationArgs]) var MaxAppTotalArgLen int @@ -680,6 +779,7 @@ func checkSetAllocBounds(p ConsensusParams) { checkSetMax(p.MaxAppProgramLen, &MaxLogCalls) checkSetMax(p.MaxInnerTransactions*p.MaxTxGroupSize, &MaxInnerTransactionsPerDelta) checkSetMax(p.MaxProposedExpiredOnlineAccounts, &MaxProposedExpiredOnlineAccounts) + checkSetMax(p.Payouts.MaxMarkAbsent, &MaxMarkAbsent) // These bounds are exported to make them available to the msgp generator for calculating // maximum valid message size for each message going across the wire. @@ -1412,6 +1512,20 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 11 // When moving this to a release, put a new higher LogicSigVersion here + vFuture.Payouts.Enabled = true + vFuture.Payouts.Percent = 75 + vFuture.Payouts.GoOnlineFee = 2_000_000 // 2 algos + vFuture.Payouts.MinBalance = 30_000_000_000 // 30,000 algos + vFuture.Payouts.MaxBalance = 70_000_000_000_000 // 70M algos + vFuture.Payouts.MaxMarkAbsent = 32 + vFuture.Payouts.ChallengeInterval = 1000 + vFuture.Payouts.ChallengeGracePeriod = 200 + vFuture.Payouts.ChallengeBits = 5 + + vFuture.Bonus.BaseAmount = 10_000_000 // 10 Algos + // 2.9 sec rounds gives about 10.8M rounds per year. + vFuture.Bonus.DecayInterval = 250_000 // .99^(10.8/0.25) ~ .648. So 35% decay per year + Consensus[protocol.ConsensusFuture] = vFuture // vAlphaX versions are an separate series of consensus parameters and versions for alphanet diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index d9c94da866..bc11070125 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -304,6 +304,11 @@ func (s *OneTimeSignatureSecrets) Sign(id OneTimeSignatureIdentifier, message Ha return OneTimeSignature{} } +// IsEmpty returns true if the verifier is empty/zero'd. +func (v OneTimeSignatureVerifier) IsEmpty() bool { + return v == OneTimeSignatureVerifier{} +} + // Verify verifies that some Hashable signature was signed under some // OneTimeSignatureVerifier and some OneTimeSignatureIdentifier. // diff --git a/crypto/vrf.go b/crypto/vrf.go index 3c74225893..0002a7b2f7 100644 --- a/crypto/vrf.go +++ b/crypto/vrf.go @@ -134,6 +134,11 @@ func (pk VrfPubkey) verifyBytes(proof VrfProof, msg []byte) (bool, VrfOutput) { return ret == 0, out } +// IsEmpty returns true if the key is empty/zero'd. +func (pk VrfPubkey) IsEmpty() bool { + return pk == VrfPubkey{} +} + // Verify checks a VRF proof of a given Hashable. If the proof is valid the pseudorandom VrfOutput will be returned. // For a given public key and message, there are potentially multiple valid proofs. // However, given a public key and message, all valid proofs will yield the same output. diff --git a/daemon/algod/api/Makefile b/daemon/algod/api/Makefile index 000825079e..59ab489183 100644 --- a/daemon/algod/api/Makefile +++ b/daemon/algod/api/Makefile @@ -33,6 +33,7 @@ server/v2/generated/model/types.go: algod.oas3.yml $(GOPATH1)/bin/oapi-codegen -config ./server/v2/generated/model/model_types.yml algod.oas3.yml algod.oas3.yml: algod.oas2.json + jq < algod.oas2.json > /dev/null # fail with a nice explantion if json is malformed curl -s -X POST "$(SWAGGER_CONVERTER_API)/api/convert" -H "accept: application/json" -H "Content-Type: application/json" -d @./algod.oas2.json -o .3tmp.json python3 jsoncanon.py < .3tmp.json > algod.oas3.yml rm -f .3tmp.json diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index e327e0e3be..b88daf40f1 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -2983,6 +2983,10 @@ "participation": { "$ref": "#/definitions/AccountParticipation" }, + "incentive-eligible": { + "description": "Whether or not the account can receive block incentives if its balance is in range at proposal time.", + "type": "boolean" + }, "pending-rewards": { "description": "amount of MicroAlgos of pending rewards in this account.", "type": "integer" @@ -3016,6 +3020,14 @@ "description": "\\[spend\\] the address against which signing should be checked. If empty, the address of the current account is used. This field can be updated in any transaction by setting the RekeyTo field.", "type": "string", "x-algorand-format": "Address" + }, + "last-proposed": { + "description": "The round in which this account last proposed the block.", + "type": "integer" + }, + "last-heartbeat": { + "description": "The round in which this account last went online, or explicitly renewed their online status.", + "type": "integer" } } }, diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index be9237b90e..96be615f79 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1067,6 +1067,18 @@ }, "type": "array" }, + "incentive-eligible": { + "description": "Whether or not the account can receive block incentives if its balance is in range at proposal time.", + "type": "boolean" + }, + "last-heartbeat": { + "description": "The round in which this account last went online, or explicitly renewed their online status.", + "type": "integer" + }, + "last-proposed": { + "description": "The round in which this account last proposed the block.", + "type": "integer" + }, "min-balance": { "description": "MicroAlgo balance required by the account.\n\nThe requirement grows based on asset and application usage.", "type": "integer" diff --git a/daemon/algod/api/server/v2/account.go b/daemon/algod/api/server/v2/account.go index addb81b432..10ec183919 100644 --- a/daemon/algod/api/server/v2/account.go +++ b/daemon/algod/api/server/v2/account.go @@ -24,6 +24,7 @@ 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/server/v2/generated/model" "github.com/algorand/go-algorand/data/basics" "golang.org/x/exp/slices" @@ -67,7 +68,7 @@ func AccountDataToAccount( }) var apiParticipation *model.AccountParticipation - if record.VoteID != (crypto.OneTimeSignatureVerifier{}) { + if !record.VoteID.IsEmpty() { apiParticipation = &model.AccountParticipation{ VoteParticipationKey: record.VoteID[:], SelectionParticipationKey: record.SelectionID[:], @@ -123,6 +124,7 @@ func AccountDataToAccount( Status: record.Status.String(), RewardBase: &record.RewardsBase, Participation: apiParticipation, + IncentiveEligible: omitEmpty(record.IncentiveEligible), CreatedAssets: &createdAssets, TotalCreatedAssets: uint64(len(createdAssets)), CreatedApps: &createdApps, @@ -137,6 +139,8 @@ func AccountDataToAccount( TotalBoxes: omitEmpty(record.TotalBoxes), TotalBoxBytes: omitEmpty(record.TotalBoxBytes), MinBalance: minBalance.Raw, + LastProposed: omitEmpty(uint64(record.LastProposed)), + LastHeartbeat: omitEmpty(uint64(record.LastHeartbeat)), }, nil } @@ -199,12 +203,16 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) { var voteFirstValid basics.Round var voteLastValid basics.Round var voteKeyDilution uint64 + var stateProofID merklesignature.Commitment if a.Participation != nil { copy(voteID[:], a.Participation.VoteParticipationKey) copy(selID[:], a.Participation.SelectionParticipationKey) voteFirstValid = basics.Round(a.Participation.VoteFirstValid) voteLastValid = basics.Round(a.Participation.VoteLastValid) voteKeyDilution = a.Participation.VoteKeyDilution + if a.Participation.StateProofKey != nil { + copy(stateProofID[:], *a.Participation.StateProofKey) + } } var rewardsBase uint64 @@ -340,6 +348,16 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) { totalBoxBytes = *a.TotalBoxBytes } + var lastProposed uint64 + if a.LastProposed != nil { + lastProposed = *a.LastProposed + } + + var lastHeartbeat uint64 + if a.LastHeartbeat != nil { + lastHeartbeat = *a.LastHeartbeat + } + status, err := basics.UnmarshalStatus(a.Status) if err != nil { return basics.AccountData{}, err @@ -350,11 +368,13 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) { MicroAlgos: basics.MicroAlgos{Raw: a.Amount}, RewardsBase: rewardsBase, RewardedMicroAlgos: basics.MicroAlgos{Raw: a.Rewards}, + IncentiveEligible: nilToZero(a.IncentiveEligible), VoteID: voteID, SelectionID: selID, VoteFirstValid: voteFirstValid, VoteLastValid: voteLastValid, VoteKeyDilution: voteKeyDilution, + StateProofID: stateProofID, Assets: assets, AppLocalStates: appLocalStates, AppParams: appParams, @@ -362,6 +382,8 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) { TotalExtraAppPages: totalExtraPages, TotalBoxes: totalBoxes, TotalBoxBytes: totalBoxBytes, + LastProposed: basics.Round(lastProposed), + LastHeartbeat: basics.Round(lastHeartbeat), } if a.AuthAddr != nil { diff --git a/daemon/algod/api/server/v2/account_test.go b/daemon/algod/api/server/v2/account_test.go index cd3c67499a..29d668f6e2 100644 --- a/daemon/algod/api/server/v2/account_test.go +++ b/daemon/algod/api/server/v2/account_test.go @@ -25,12 +25,15 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/basics" + ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) func TestAccount(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() + proto := config.Consensus[protocol.ConsensusFuture] appIdx1 := basics.AppIndex(1) appIdx2 := basics.AppIndex(2) @@ -203,3 +206,21 @@ func TestAccount(t *testing.T) { } }) } + +func TestAccountRandomRoundTrip(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, simple := range []bool{true, false} { + accts := ledgertesting.RandomAccounts(20, simple) + for addr, acct := range accts { + round := basics.Round(2) + proto := config.Consensus[protocol.ConsensusFuture] + conv, err := AccountDataToAccount(addr.String(), &acct, round, &proto, acct.MicroAlgos) + require.NoError(t, err) + c, err := AccountToAccountData(&conv) + require.NoError(t, err) + require.Equal(t, acct, c) + } + } +} diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 4a3438b8ff..caabafbfd7 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -192,134 +192,136 @@ var swaggerSpec = []string{ "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", - "npw8mnz8/wEAAP//6aRdnSH5AAA=", + "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", + "AAAA////ElqSu/oAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index e7e56520a4..b0e581df69 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -168,134 +168,136 @@ var swaggerSpec = []string{ "2q98kbD4EvARbiG+Y8SN1vF/2/0KcntvvV29/ODBLtV6mZmzHV2VMiTud6apHbQwQpaPxlBsgdqqK7M0", "A5IvIb9x9W9gVendtPO5D/hxgqZnHUzZykg2Mw9rc6CDYgakrgrqRHHKd/0iCQq09mHFb+EGdleiLe1x", "TFWEbpK+Sh1UpNRAujTEGh5bN0Z/811UGSr2VeVz3THp0ZPFWUMX/pv0QbYi7z0c4hhRdJLIU4igMoII", - "S/wJFNxioWa8O5F+bHlGy5jZmy9SJcnzfuJeaZUnFwAWrgat7vb5CrDMmtgoMqNGbheuQphNRA+4WK3o", - "AhIScugjGpnu3fEr4SCH7r3oTSfm/QttcN9EQbYvZ2bNUUoB88SQCiozvbA/P5N1QzrPBBb+dAiblSgm", - "NfGRlulQ2fHV2UqGKdDiBAyStwKHB6OLkVCyWVLli5dhjTd/lkfJAH9gYYV95XQugoi1oJBbUyzH89z+", - "OR1ol66ojq+k48vnhKrliFI4RsLHIPnYdgiOAlABJSzswu3LnlDaIg/tBhk4fprPS8aBZLHgt8AMGlwz", - "bg4w8vEjQqwFnoweIUbGAdjoXseByY8iPJt8cQyQ3BWpoH5sdMwHf0M8fcyGgxuRR1SGhbOEVyv3HIC6", - "iMnm/urF7eIwhPEpMWxuTUvD5pzG1w4yqOqCYmuvhosL8HiYEmf3OEDsxXLUmuxVdJvVhDKTBzou0O2B", - "eCa2mc0fjUq8s+3M0Hs0Qh6zWWMH09bPeaDITGwxaAivFhuRfQCWNBwejEDD3zKF9IrfpW5zC8y+afdL", - "UzEqVEgyzpzXkEtKnBgzdUKCSZHLF0FJnFsB0DN2tPWlnfJ7UEntiifDy7y91aZtqTeffBQ7/qkjFN2l", - "BP6GVpimiM2bvsQStVN0Y1+69XsCETJG9IZNDJ00Q1eQghJQKcg6QlR2E/OcGt0G8Ma59J8FxgusEkT5", - "7mEQUCVhwZSG1oju4yQ+h3mSYnFCIebp1elKzs363grRXFPWjYgfdpb5yVeAEclzJpXO0AMRXYJ56TuF", - "SvV35tW4rNQN2bKlfFkR5w047Q3ssoKVdZxe3bw/vDTT/tiwRFXPkN8ybgNWZlh6OhrIuWdqG+u7d8Gv", - "7IJf0Xtb77jTYF41E0tDLt05/kXORY/z7mMHEQKMEcdw15Io3cMggwTcIXcM5KbAx3+yz/o6OEyFH/tg", - "1I5PA07dUXak6FoCg8HeVTB0ExmxhOmgcvMwMzZxBmhVsWLbs4XaUZMaMz3K4OHr3fWwgLvrBjuAgW5c", - "XjTMuVMr0EX/OZvPKQrIp0aEs+GALtYNJGo5Nie0qCUa1TrBdsPClI1gN3LtP/xyqYWkC3CG0cyCdKch", - "cDnHoCEo+6iIZtbDWbD5HEKDoLqNMasDXN/sE23uMILI4lbDmnH91fMYGR2gnhbGwyiLU0yEFlJuoquh", - "4dWLVYHe2XQuCbbmFtbTaAbpD7DLfjEaCqkok6qNGHOW0C7/O2LX16sfYIcjHwzEMoAd2BVUU98C0mDM", - "LNg8sokTjQoU1jDFog+dLTxip87ju3RPW+OqzqaJvw3L7lRl7S7lLgej9dsZWMbsxmXcXWZOD3QR3yfl", - "Q5vAEsa4kBwDkSuciinfo2d4FTXp0Ydo9wpo6YkXlzP5OJ3czTkVu83ciAdw/aa5QKN4xuAn66zo+JqP", - "RDmtKinWtMycCy91+Uuxdpc/vu49fp9YmIxT9tW356/eOPA/Tid5CVRmjTKWXBW+V/3LrMrWqd1/laDE", - "4q0iVlkPNr8prhm6/TZLcM0UAn1/UPW5dekGR9G5AefxGMyDvM95n+0S93ihoWqc0K2DxPqgu35nuqas", - "9J4JD20iXhIXN650eJQrhAPc2X8dhCFk98puBqc7fjpa6jrAk3Cun7BaWlzj4K6WGrIi54+m9y49fSdk", - "h/m7ZJmoP/uPE6uMkG3xmAgf9A16+sLUCbGC12+L38xpfPQoPGqPHk3Jb6V7EACIv8/c76hfPHoUdTVE", - "LQmGSaChgNMVPGwCf5Mb8WnNThw24y7o8/WqkSxFmgwbCrWOaY/ujcPeRjKHz8L9UkAJ5qfDuXW9Tbfo", - "DoEZc4IuU8kxTdzTyvYEUkTwfpgf5mUZ0kJmv6JY9dx6boZHiNcr9HZkqmR53A/MZ8qwV27je8zLBF9O", - "GMzMiDVLhIvxmgVjmdfGlPHrARnMEUWmilYSbHE3E+5415z9swbCCqPVzBlIvNd6V51XDnDUgUBqVM/h", - "XG5gG0XQDn8XO0hY8b8vMyIQ+40gYTTRANyXjVnfL7TxmrU607FBieGMA8a9J6DQ0YejZptgsexGBY3T", - "Y8b0hvSMzrUeSMwR7fXIVDaX4neI26LRhB/JzfY9DhhG4v4OoXoWdjjrsJTGA9W2rGxnP7Td43Xj1Mbf", - "WRf2i27aKtzmMo2f6uM28jZKr4pXEHVITilhoTuyG62aYC14vIL4LKxo70MVKLfnySYmd5Ie4qcyTC86", - "teO3p9LBPEjJKulmRmPl/o0uZGAKtrcTVKEF8R/7DVBN2q2dnQRBhc27zBY3qkC2tSmGhRJvqdfYaUdr", - "NK0CgxQVqi5TGwhWKhEZpuYbym2bRPOd5VfuawXWC2q+2giJpclUPP6jgJytoubY6+t3RT709RdswWwH", - "wFpB0GLODWS7q1oqcm36mmRyh5qLOXk8Dfpcut0o2JopNisB33hi35hRhddl45FsPjHLA66XCl9/OuL1", - "Zc0LCYVeKotYJUije6KQ10QxzUBvADh5jO89+Zp8gfFbiq3hocGiE4ImZ0++Ru+7/eNx7JZ1HRz3sewC", - "efbfHc+O0zEGsNkxDJN0o55EqzjZFs7p22HPabKfjjlL+Ka7UA6fpRXldAHxkOHVAZjst7ib6FHt4YVb", - "bwAoLcWOMB2fHzQ1/CmRhmjYnwWD5GK1YnrlonyUWBl6avvH2Un9cLaZqWv94eHyDzFYrvKxQj1b1ydW", - "Y+gqkUaAIY0/0hV00Tol1NajK1kbxuobEpELX+4Se6E0LVAsbsxcZukoS2JU65xUknGN9o9az7O/GLVY", - "0tywv5MUuNnsq+eRniLdsvv8OMA/Od4lKJDrOOplguy9zOK+JV9wwbOV4SjFwzbtNziVyai+ePxWKohs", - "/9BjJV8zSpYkt7pDbjTg1HciPL5nwDuSYrOeo+jx6JV9csqsZZw8aG126Oe3r5yUsRIyVsO6Pe5O4pCg", - "JYM1JnHEN8mMece9kOWoXbgL9J83BMWLnIFY5s9yVBEIPJr78jeNFP/L67YYLzpWbXJMzwYoZMTa6ex2", - "nzjg6zirW99/a2N28FkCc6PRZju9D7CSCNW1sbjNN584nTdq7rV73jE4PvmNSKODoxz/6BEC/ejR1InB", - "vz3tPrbs/dGjeE3MqMnN/Npi4S4aMX4b28NvRMQA5htQNQFFLmU3YoBMXVLmgWGCMzfUlHSb/Xx6KeJ+", - "kkHiAX/xU3B9/Q6feDzgH31EfGZmiRvYhjSnD3u32VmUZIrmeRBqTMk3YjuWcHp3kCeePwGKEigZaZ7D", - "lQyauUXd9QfjRQIaNaPOoBRGyQz7VIT2/H8dPJvFT/dgu2Zl8Utbbqh3kUjK82U0UHNmPvy1bbreLNGy", - "ymjp+yXlHMrocFa3/dXrwBEt/R9i7Dwrxke+228maJfbW1wLeBdMD5Sf0KCX6dJMEGK1W8mlyRQuF6Ig", - "OE9bZ71ljsOunEGrsH/WoHTsaOADm62Ezi7DfG2nKgK8QOvXCfkeayoYWDpFdNHq5MsTdkt11VUpaDHF", - "solX356/InZW+41tHWw7ZS3Q6NJdRdRKPr50WdMFOJ6TP36c/UnCZtVKZ01jq1jVI/NG23qL9UIn0BwT", - "YueEvLSWMOXtLHYSgsU35QqKoI+W1cWQJsx/tKb5Ek1MnYssTfLjW7x5qmwN8EG/6KavAp47A7fr8mab", - "vE2J0EuQG6YAszBhDd1CS03VMWfi9IWXusuTNeeWUk6OkCmaLgrHot0DZwUS7xuOQtZD/JEGBtsh8diO", - "d5f4VbTMc799Xs9568v2NH2AXzsbcU654CzHIssxgQiLwozzNo2oRx13E6mJO6GRwxVt2tfkfzksJtv4", - "eUboEDf03AZPzaZa6rB/ati6Zi4L0MpxNiimvvek82swrsD1yTBEFPJJISOxKdF49sYPfiQZYb2HhKHq", - "O/PsR2fGxEToG8bRYOHQ5sRs63koFUMHIydMk4UA5dbTLXql3plvTrD+UwHb9yevxILll2yBY9hoKLNs", - "G/o3HOrcBwK6wDvz7gvzrqvK2/zcieqxk55XlZs03Zk03o55y5MIjoWf+HiAALnN+OFoe8htbwQv3qeG", - "0GCNwUdQ4T08IIymS2evJbZRESxF4RvE5iZFS/MxHgHjFePeExa/IPLolYAbg+c18Z3KJdVWBBzF066A", - "lok4dsz1s67Uuw7Vr0lsUIJr9HOkt7FtMJpgHM0LreBG+Y74Q2GoOxAmXtCyiYCNtAtFqcoJUQXmiPQa", - "iMYYh2HcvkVx9wI40JV82n6Odb6PvYlS1Y9mdbEAndGiiLUt+QafEnzqc31gC3ndtLeoKpJjsc9u9dMh", - "tbmJcsFVvdozl3/hjtMFHXkj1BB2BfY7jNUVZjv895h+8U3s69H5bT7QtTiu5O8wXy8m9RqazhRbZOMx", - "gXfK3dHRTn07Qm+/v1dKL8WiC8jnMJImuFy4RzH+9q25OMKSgIMwY3u1NBX7MKRX4HNf5KKpNdXlSniV", - "DTqYoPO66dO+3wyR7rg+xcsvkVMamrzt/WrNwKnM0jyZCE21K8miKdnLgpJlLmzIZ8+IPvQEpcI8bZTn", - "/Rmf3Vr3IjTtgvmh43CxoT4ts0g6Wm7nC2k3+FhnyA/rVLKxrwCOz/sdmW/A1WmrJKyZqH0QjQ9l9Sqh", - "/bXT37hJ946uPxog/rmNz0lT+ZXrjGeX6XTyH36xzjQCXMvdn8BwPtj0Qa/nobRrzVPtK6RpqjSqyVLn", - "VhxTHT9WiN3Jhp1u0wd6ZQ/I6uUYcWDY+3o6uSiOujBjxfwndpTYsYt3sk7XOm7rG+MRq4RibW+zWIvr", - "kTHjV9ilOqjVPBzLxxKuIdfY0K6NkZIAx1RuNpN52/3/q3mcVqeb0HpX6nhffeNhF7sDd/ygBElQRsd2", - "ADsZX833vImEtYk8G6qw9r1EG3c39XV0At58Drlm6wMlX/6+BB6UE5l6uwzCMg8qwLAmHQUrhh5vdWwB", - "2leRZS88QeX+O4OTSke+gd0DRTrUEG1J1uRi3aZYJGIAuUNmSESoWKSZNSS74B+mGspALPjITvs5tGW3", - "k92MgwJGt5zLk6S5ONqiRnumjLdTHTWX+fSoUl+YWZGqCjPsxpjWP15i80vl4pxoU2wy1NLJxbAk/8YV", - "q8QCPY3vxJetBOV/89W47Cwlu4Gw3zJ6qjZUFv6NqOnFW3WyPffRoJSL7yTYB3rezMzaOPyhrzpS5BlT", - "WvJSGDEiS+UFdUPfm7ixB8oG+LV1WBCuOUjXlx7l31IoyLTwcfv74NiHChvFeCskqGRjBQtcstzp27ae", - "KzaYoVjelLrgxXCBRMKKGuhkUHU1Pec+ZL+wz30utW8wctDC1NDr4U53PgODqQESQ6qfE3dbHs7Rvo2x", - "iXEOMvOep34JVg6y6w2ppCjq3F7Q4cFoDHKjS6DsYSVRO00+XGVPRwhynW9gd2qVIN8i0O9gCLSVnCzo", - "Qem+3ibfq/lNxeBe3At4n9NyNZ1UQpRZwtlxMawb26f4G5bfQEHMTeEjlRPdX8kXaGNvvNmb5c7XSa0q", - "4FA8PCHknNvcEO/Y7jYu6k3OH+h9829x1qK2pZydUe3kmseD7LHIsrwjN/PD7OdhCgyru+NUdpADVUm3", - "iZq1km4ivZBPxmrlQ1dzvz9tS1QWiphMcmk9Vi/woMcMR5jJHpRcQEcmJc7TRVQpYiGZt8m2N0PFMRVO", - "hgBp4GOSvhso3OBRBEQ7rkZOoa1g5mqXiTmR0DqRb1vEbdgcNqbR92duZunyu7mQ0Gnzar4WsvAiD1Nt", - "P2YqZ0xLKne3KbU2aE47sJ4ksXwwHKuJxGoX0kZjDXFYlmKTIbPKmtrmMdXWvKe6l7Fv59J+Z071DIK4", - "LqqcoLYjS1qQXEgJefhFPG3PQrUSErJSYJhXzAM910buXmGuDielWBBR5aIA2yMgTkGpuWrOKYpNEETV", - "RFFgaQeTPu03AR2PnPK+OiPb4jx20Zn1ZSYCT0G5YjwOQ/blIbx7ugofVZ3/Yo4WIYaxLt3cayt9hr2V", - "4cjWyqwsvcEg1V2Z/KxqDEfCxBszxXOyEko7zc6OpJqh2hCvL3LBtRRl2TUCWZF44Szbr+n2PM/1KyFu", - "ZjS/eYh6JBe6WWkx9Wmp/WC8dibZq8g0sg301TJi58VZ/Kk7utez4xxHt2gNwHx/mGMdtnGfx1pZd9fV", - "783OE7UztVixPE7D/1rRbcmYtBhLiJZ6sl2SbHI+voaMOrwcmmAGZElDNAM3BBvbL8fTnFMXmYf5L0q8", - "/XHJHNwlkbiYhnzSSS1ZnpStegAgpDZjVNfStlYKJZ+Gq4iFzTBHl3Qf0JFcHCN/7gabGeHegdJwJ6AG", - "0YYNgF9YZX9qS3LZyMWZ2PrnD9uaXbcC/uN+Ko+1o4+c4oa0XLd8X98jwRHilYH3xh9h43B/gx6OQmra", - "4I28UQMA0nFJHRhGRScdC8acshKKjOrE5Y42oWmg2bqMln5zU6YcJ8+pvbCXQMzYtQRXb8KK1L1m6BU1", - "pCSa14eWW17AFhQWg7Adnamyfgbv74DStpXqKd+iykpYQydcyxXBqFG0Y2vw36rmY1IAVOj969ukYnFI", - "4V3eM1S4tWdBJMsY7EYtFxaxdqfIAbNE1Iiy5Zk9JmrsUTIQrVlR0w7+1LEiR9fsZo5yBFUDmTzzetvY", - "aX62I7z1A5z772OijMfE+3F86GgWFEfdPgZ0MC6xVqlTz+NhiWGFl8ahgbMVjePTknjLN1RFNzxtAByS", - "fKvejNwnJniA2G+3kKNU0427uztOCA5GVK96U1IEl80O396Q/FloeC8JJ8eLqRoKkMHutdR4unACO76A", - "7Sy5EXuN1IwtpBz/d/xvih347UBGr7YdrUIN7iV4jx0WlG6cFU6gZc2F5uMLp66eYF8pZ0Fk9YruiJD4", - "j9HX/lnTks13eEIt+P4zopbUkJBzEVrftYtXNBPvF0ymHjBvFxB+KrtuNnbMYLidGSUA2lyBzjiFlYFu", - "INwGdMtbzpNrw3JUPVsxpfCy623nEAtu8b4mxIoWoY6Mlem6rUR9rVLz9f/fZm2FU/mCUlVJc9+/DIii", - "q55B3PYo9MSll7Dan9Y3VI89CTR9D1uilT6dt7iFce/IyI1YrHyq30MH7EE/uEGrizst45gGxW1m9J6E", - "yFFLue9dGBsfMgAancy+qtcB8G01Rl8B7FPgP1o0MrWMMeD/WfCeaKMXwms75n0CLHdS/iOwWrvqTGwz", - "CXN1KBTCGlaNIizbYgHeOMl4LoEqGxty8ZNT2dqaiIwbFdJGLzbet2aUAuaMt8yS8arWEQ0ASyPyXYCw", - "0DyNaE04e1JSghHD1rT8aQ1SsiK1ceZ02DZeYU16b5J330aU/+ZOHQ7AVKv9YCYhtJlqwWvmArddb2xg", - "odKUF1QW4euMkxykuffJhu7U7X0fBlpZG/nigPeDBtJMN7898IMgaVtAyp1zX97RM9EASO/RRTHCtYAR", - "rBG3gjWKaJHwJAxhiJdVoNusFAvML0sQoCs+ib4fq6wIjgZbKw8dN49iv8P+abDutjv4WuCsY6bYf85+", - "QtShwvMzZ3rvSbPWtH7Cn43ItAfB0z9ftGHhdnOG9B/L0bzCJIZOnma/6bzfaxseYueDhCeja8FN7CI6", - "yF2Cb2iuHd/PqOuDj2WCWh02Q91W7Qn8BtUGOdPcBe4MjT4DpdgiZeryaI+0CVlLsr8HEuDZTrXubHWn", - "bYIpzDjHNIHanzmbVaLK8jHRgLY0f+EM2g7SLowJ+gjM1Yl1N4ETqmlW0Sls0ulacWwfrGTXjEN+mSrf", - "p2SnDBoJDto1los58jI8wtaMgzkejfFi2s8+6hpsGiZBKJGQ1xINmhu6O9xXKFES9vJv518+efrr0y+/", - "IuYFUrAFqLascK8vTxsxxnjfzvJpY8QGy9PxTfB56RZx3lPm022aTXFnzXJb1dYMHHQlOsYSGrkAIscx", - "0g/mVnuF47RB33+u7Yot8t53LIaCP37PpCjLeFn3RnSLmPpjuxUY+43EX4FUTGnDCLu+OqbbWFm1RHMc", - "Fvdc2zojgueu+npDBUwngnFiC0mFWiI/w6xf598gsK1Kx6usT2LfupxeZC1iGJyB8RszIJWonCjN5iQG", - "EeaWyCDn0hkaMbwziJ5smK2No4wRootJjpPeOXeap5iT/dy+261Rxzm92cSIeOEP5S1IM2VJT2e034aT", - "tKb0Pw3/iKTo3xvXaJb7R/CKqH5wu8bHo0AbpmtHyAMBSORhdjLowr7obaVRaa3yaL/3rs6++PG6dYEe", - "TBhASPwHB8ALEyvb95oYdwfOZy7Z+bpBSrCU9ylK6Cz/UK6mZ73NRRJskTNSaA3KsiUxFAuDRFz1oslv", - "TWglgzRYbIJuNNOyjKTPWrsJnqmQcIxKINe0/PRcA7vjnyM+oHibTpoJcyhDJFtUqttVcHtFR80d5Eve", - "39T8Dabs/h3MHkXvOTeUcxcPbjO0emFL6oW/FWwWMNngmDYc6MlXZOaq6VcScqb6buiNF06alEGQbO5C", - "L2GrD+QoHlrnL0LfgYznPmaE/Bi4kwSa7VoI2yP6mZlK4uRGqTxGfQOyiOAvxqPC7psHros7Vl6/XUGQ", - "oLTXkQVBhn1Fxy7PFr0wl06tYLjO0bd1B7eRi7pd29hqNqMLuF9fv9OzMUVo4sXWzedYBedeqq4fVXP9", - "D6h/Y3HkxnDzxijml1RFVFv1M1F8t7cfNSsPBoh0Sil/nE4WwEExhcWCf3XNIT7tXeohsDn5w6NqYb1L", - "IRGLmMhaO5MHUwVFkkfUR3afRaohY75bXkumd9gY1BvQ2K/RSj3fN1UfXNWQxnfl7j4tbqBpztzWiKiV", - "v12/F7TE+8i61Li5hUR5Qr7d0lVVOnMw+euD2X/As788Lx4/e/Ifs788/vJxDs+//PrxY/r1c/rk62dP", - "4Olfvnz+GJ7Mv/p69rR4+vzp7PnT5199+XX+7PmT2fOvvv6PB4YPGZAtoL5299nkf2Xn5UJk528usisD", - "bIsTWrEfwOwN6spzgY3rDFJzPImwoqycnPmf/oc/YSe5WLXD+18nrgHLZKl1pc5OTzebzUn4yekCk8Iz", - "Lep8eernwXZiHXnlzUUTTW7jXnBHW+sxbqojhXN89vbbyyty/ubipCWYydnk8cnjkyeudy2nFZucTZ7h", - "T3h6lrjvp47YJmcfPk4np0ugJdZQMX+sQEuW+0cSaLFz/1cbuliAPMGEAfvT+umpFytOP7jk+I/7np2G", - "IRWnHzo1BIoDX2I4wOkH38Fy/9ud7oUuEiv4YCQU+147nWHXirGvggpeTi8FlQ11+gHF5eTvp87mEX+I", - "aos9D6e+0Eb8zQ6WPuitgfXAF1tWBCvJqc6XdXX6Af+D1BsAbYswnuotP0XP6emHzlrd48Fau7+3n4dv", - "rFeiAA+cmM9tZ899j08/2H+DiWBbgWRGLLSFT5yXuDl0F8XkbPJt8NKLJeQ3E+wGhjF7eJqePn4cqVAb", - "fEXs4aazEgpzMp8/fj7iAy50+JFLyBp++DO/4WLDCdYztJy+Xq2o3KEEpWvJFfnpB8LmBPpTMOVnQO5C", - "Fwp9Q/WsZPlkOumg5/1HhzRbv+sU+1/tWlz6n3c8j/443OZO7aLEz6f+bomxl+6bHzp/dk+VWta6EJtg", - "FtTKrElhCJl5WKv+36cbyrSRs1zJHGyYOfxYAy1PXX3s3q9tScrBE6yzGfwYBqdHfz2lDtWTSqgI2b6l", - "m8CUeo4vW2EElP5GIFefuJY6vXIup9tsxjhS0IeJavqIt8KYfTjU5ga3mtFNMerA27OG6e6YcysFLXKq", - "sFGjKzU/CSUnLWv4GD12eJwe71mLu62Cdey1LXaKgkZW9A0tiE9VzshrWhqsQEHO3ZXfWZo97E8+HXQX", - "3AbOmsNtpZ6P08mXnxI/F9wI6LT07MhM/+zTTX8Jcs1yIFewqoSkkpU78jNvYn9vzUi/Q+KUNL9B4awh", - "WBuoIummG04s46mg3U4KPjMYiN6SJeVF6ZLnRI1NWA1lof1ZBB5QcwH5TiKVkAiALdEEhfUJqRNy2XjM", - "0P9kA9exIdIaSlGhgQgLD9pJKHrTrEU1vAi6/N9om+YQL4Bnjo1kM1HsfF9zSTd6a/PgBryqaVAffdiX", - "zmJPnXSSeMlHqvnHraYWaj6Ts3eBzvPu/cf35plcY0jNuw+BIH92eoqhy0uh9Onk4/RDT8gPH75vEOYb", - "Sk0qydZYMRmRJiRbME7LzAnQbdORydOTx5OP/zcAAP//nxvBKMzzAAA=", + "S/wJFNxioWa8O5F+bHmM58A1W0MGJVuwWayo49+H/jAPq6FKV8fKRSE3AyrC5sSo8jN7sTr1XlK+AHM9", + "mytVKFraGn3RoA3Uh5ZApZ4B1Xvt/DxMxvfQoUq5MSfLWvimZgmwNfvNNFrsOGyMVoGGIvuOi14+Scef", + "WcChuCU8/vNWUzhJ6roOdZH6Vf5WbrDbqLUuNC+kM4TLPl8BFsATG7MvBgrharfZEgHB/VIruoCE7hJ6", + "70Ym4nc8fjjIIYkkKoOIeV/UGEgCUZDty5lZc/QMg3liDjGqmb2ATD+TdRA7nxGWZHUIm5UowDaRq3bv", + "qex4UW2NyRRocdYCkreioAeji5HwOC6p8scRq+95LjtKOvsDS17sK3R0EcQSBiX2mjJG/jbsc9CB3u/K", + "HfkaR76wUaj0jyhSZHQvTF+IbYfgKJoWUMLCLty+7AmlLb/RbpCB46f5HHlLFgtLDAzUgQDg5gCjuTwi", + "xPpGyOgRYmQcgI2BDzgw+VGEZ5MvjgGSu/Ih1I+NV0TwN8QT+2ygvhFGRWUuV5bwN+aeA1AXy9pIFr2I", + "ahyGMD4lhs2taWnYnNPF20EG9XZQoehV13GhNw9TisYe15S98o9akxUSbrOaUJr1QMdF7T0Qz8Q2s5m9", + "UV1ktp0Zeo/mLmCecexg2spGDxSZiS2Gc+HVYmPlD8CShsODEdhetkwhveJ3KTnLArNv2v1ybowKFZKM", + "M7Q25JIS9MZMnZAtU+TyRVCs6FYA9MxQbeVvZ5Y4aD7oiifDy7y91aZtET6fFhY7/qkjFN2lBP6G9rGm", + "vNCbvsQStSB1o5K6lZUC4T5G9IZNDN1nQyedghJQXcs6QlR2E/NpG60T8Ma59J8FZiWs30T57mEQ6iZh", + "wZSG1r3hI1g+h+GYYtlIIebp1elKzs363grRXFPWwYsfdpb5yVeAseJzJpXO0DcUXYJ56TuF5o7vzKtx", + "WakbTGeLLLMizhtw2hvYZQUr6zi9unl/eGmm/bFhiaqeIb9l3IYSzbAoeDTEds/UNgp774Jf2QW/ove2", + "3nGnwbxqJpaGXLpz/Iucix7n3ccOIgQYI47hriVRuodBBqnRQ+4YyE1B9MXJPrv44DAVfuyD8VQ+QTt1", + "R9mRomsJTDl7V8HQgWfEEqaDmtrDnOXEGaBVxYptz0ptR01qzPQoU5SvRNjDAu6uG+wABroRk9EA9E4V", + "RxeX6axxpyggnxoRzgZquihEkKjl2GzdopZo7uyEQQ5LhjaC3ci1//DLpRaSLsCZrDML0p2GwOUcg4ag", + "IKcimlnfc8HmcwhNteo2ZsYOcAODXDGCdCNEFrfn1ozrr57HyOgA9bQwHkZZnGIitJBy4F0NTeJerAr0", + "zqanTLA1t7BrR3N7f4Bd9ovRUEhFmVRtLJ+zUXf53xG7vl79ADsc+WCInAHswK6gmvoWkAZjZsHmkU1p", + "aVSgsLosluPobOERO3Ue36V72hpXDzhN/G3AfKdebncpdzkYrUfVwDJmNy7jjkxzeqCL+D4pH9oEljDG", + "heQYiFzhVEz57knDq6hJXD9Eu1dAS0+8uJzJx+nkbm7D2G3mRjyA6zfNBRrFM4alWTdSJwrgSJTTqpJi", + "TcvMOVdTl78Ua3f54+veF/uJhck4ZV99e/7qjQP/43SSl0Bl1ihjyVXhe9W/zKpsBeH9VwlKLN4qYpX1", + "YPObsqehQ3azBNfmItD3B/W4W2d7cBSdg3Yej449yPtcXIBd4p74AKia8IDWQWKjA7oRAXRNWek9Ex7a", + "RCQrLm5cUfcoVwgHuHNkQRAgkt0ruxmc7vjpaKnrAE/CuX7COnZxjYO7KnfIilykAL136ek7ITvM36Ux", + "RSMN/jixygjZFo+JwE7fOqkvTJ0QK3j9tvjNnMZHj8Kj9ujRlPxWugcBgPj7zP2O+sWjR1FXQ9SSYJgE", + "Ggo4XcHDJiQ7uRGf1uzEYTPugj5frxrJUqTJsKFQGzLg0b1x2NtI5vBZuF8KKMH8dDjrsbfpFt0hMGNO", + "0GUqbamJSFvZbk2KCN4PwMSMOUNayOxXFOvRW8/N8AjxeoXejkyVLI/7gflMGfbKbeSVeZngywmDmRmx", + "ZolAPl6zYCzz2pgCiz0ggzmiyFTRGo8t7mbCHe+as3/WQFhhtJo5A4n3Wu+q88oBjjoQSI3qOZzLDWyj", + "CNrh72IHCXsx9GVGBGK/ESSM8xqA+7Ix6/uFNl6zVmc6Nlw0nHHAuPeEejr6cNRsU1+W3XitcXrMmK6d", + "ntG5phCJOaJdOJnK5lL8DnFbNJrwI1nzvvsEwxjp34HHwnz6LKXxQLXNRNvZD233eN04tfF31oX9opuG", + "F7e5TOOn+riNvI3Sq+K1XR2SU0pY6I7sxhEnWAseryByDnsN+FAFyu15sinjnXSU+KkME79O7fjtqXQw", + "D5LlSrqZ0VgjBqMLGZiC7e0EVWhB/Md+A1STEG1nJ0G4Z/Mus2WnKpBt1ZBhCctb6jV22tEaTavAIEWF", + "qsvUBoKVSkSGqfmGctvA0nxn+ZX7WoH1gpqvNkJi0TgVj/8oIGerqDn2+vpdkQ99/QVbMNubsVYQNP9z", + "A9m+t5aKXAPFJs3foeZiTh5Pgw6kbjcKtmaKzUrAN57YN2ZU4XXZeCSbT8zygOulwtefjnh9WfNCQqGX", + "yiJWCdLonijkNVFMM9AbAE4e43tPviZfYPyWYmt4aLDohKDJ2ZOv0ftu/3gcu2Vdb819LLtAnu0jO+N0", + "jAFsdgzDJN2o8VBN21w7fTvsOU320zFnCd90F8rhs7SinC4gHsy9OgCT/RZ3Ez2qPbxw6w0ApaXYEabj", + "84Omhj8lEkQN+7NgkFysVkyvXJSPEitDT21nPzupH862mXVNWTxc/iEGy1U+Vqhn6/rEagxdJRI8MKTx", + "R7qCLlqnhNpKgSVrw1h9qyhy4QuRYpeapjmNxY2ZyywdZUmMap2TSjKu0f5R63n2F6MWS5ob9neSAjeb", + "ffU80u2l2xCBHwf4J8e7BAVyHUe9TJC9l1nct+QLLni2MhyleNgmZAenMhnVF4/fSgWR7R96rORrRsmS", + "5FZ3yI0GnPpOhMf3DHhHUmzWcxQ9Hr2yT06ZtYyTB63NDv389pWTMlZCxqqLt8fdSRwStGSwxvSa+CaZ", + "Me+4F7IctQt3gf7zhqB4kTMQy/xZjioCgUdzX2atkeJ/ed2WSUbHqk1b6tkAhYxYO53d7hMHfB1ndev7", + "b23MDj5LYG402mwP/gFWEqG6Nha3+eYTJ1pHzb12zzsGxye/EWl0cJTjHz1CoB89mjox+Len3ceWvT96", + "FK9WGjW5mV9bLNxFI8ZvY3v4jYgYwHxrsCagyCVTRwyQqUvKPDBMcOaGmpJuG6ZPL0XcTzJIPOAvfgqu", + "r9/hE48H/KOPiM/MLHED25Dm9GHvtqGLkkzRPA9CjSn5RmzHEk7vDvLE8ydAUQIlI81zuJJBm72ou/5g", + "vEhAo2bUGZTCKJlhB5HQnv+vg2ez+OkebNesLH5pC0H1LhJJeb6MBmrOzIe/tu3wmyVaVhltSrCknEMZ", + "Hc7qtr96HTiipf9DjJ1nxfjId/ttHu1ye4trAe+C6YHyExr0Ml2aCUKsdmvsNDnc5UIUBOdpK+C3zHHY", + "LzVo4vbPGpSOHQ18YLOV0NllmK/tIUaAF2j9OiHfY7ULA0unvDFanXzhyG4RtboqBS2mWNDy6tvzV8TO", + "ar+xTZ1tD7MFGl26q4hayccXlWv6M8erJYwfZ3/6tlm10lnTcixWj8q80TZFY73QCTTHhNg5IS+tJUx5", + "O4udhGBZVLmCIuhwZnUxpAnzH61pvkQTU+ciS5P8+OZ7nipbA3zQybvpeIHnzsDt+u/Z9ntTIvQS5IYp", + "wCxMWEO3BFZTD86ZOH1JrO7yZM25pZSTI2SKpr/FsWj3wFmBxPuGo5D1EH+kgcH2rjy2F+ElfhUtwN1v", + "bNhz3vqCSk2H5tfORpxTLjjLsfx1TCDCcj3jvE0jKoXH3URq4k5o5HBF2yk2+V8Oi8kGi54ROsQNPbfB", + "U7Opljrsnxq2rs3OArRynA2Kqe8K6vwajCtwHUwMEYV8UshIbEo0nr3xgx9JRliJI2Go+s48+9GZMTER", + "+oZxNFg4tDkx23oeSsXQwcgJ02QhQLn1dMuRqXfmmxOszFXA9v3JK7Fg+SVb4Bg2Gsos24b+DYc694GA", + "LvDOvPvCvOvqJTc/d6J67KTnVeUmTfeMjTfK3vIkgmPhJz4eIEBuM3442h5y2xvBi/epITRYY/ARVHgP", + "Dwij6Z/aa1ZuVARLUfgGsblJ0aKJjEfAeMW494TFL4g8eiXgxuB5TXynckm1FQFH8bQroGUijh1z/awr", + "9a5D9atFG5TgGv0c6W1sW78mGEfzQiu4Ub4j/lAY6g6EiRe0bCJgI41cUapyQlSBOSK91q4xxmEYt28e", + "3b0ADvSLn7afYwX2Y2+iVF2qWV0sQGe0KGLlTL7BpwSf+lwf2EJeN41HqorkWIa1W5d2SG1uolxwVa/2", + "zOVfuON0Qa/kCDWE/Zr9DmN1hdkO/z2mk38T+3p0fpsPdC2OK8Y8zNeLSb2GpjPFFtl4TOCdcnd0tFPf", + "jtDb7++V0kux6ALyOYykCS4X7lGMv31rLo6wWOMgzNheLU0tRQzpFfjcF7loqoB1uRJeZYPeMui8bjro", + "7zdDpHvhT/HyS+SUhiZve79aM3AqszRPJkJT7UqyaEr2sqBkmQsb8tkzog89QakwTxvleX/GZ7fWvQhN", + "u2B+6DhcbKhPyyySjpbb+ULaDT7WGfLDOpVs7Guz4/N+r+wbcBX0KglrJmofRONDWb1KaH/tdJ5u0r2j", + "648GiH9u43PSVH7lehbaZTqd/IdfrDONANdy9ycwnA82fdCFeyjtWvNU+wpp2l2Nan/VuRXH9C2Ilch3", + "smGnD/iBLuYDsno5RhwYdiWfTi6Koy7MWJuFiR0lduziPcbTVajbytN4xCqhWNt1LtZ8fGTM+BX2Dw+q", + "aA/H8rGEa8g1thpsY6QkwDE1tc1k3nb//6pRp9XpJrTeFaHeV3l62F/wwB0/KEESlNGxvdlOxtdZPm8i", + "YW0iz4Yq7Eog0cbdTX0dnYA3n0OOlTD3lnz5+xJ4UE5k6u0yCMs8qADDmnQUrOV6vNWxBWhfRZa98AQ9", + "Fe4MTiod+QZ2DxTpUEO0WVyTi3WbYpGIAeQOma8bmjIku+AfphrKQCz4yE5XfrMtiJ6s8xkUMLrlXJ4k", + "zcXRFjXaM2W80e2oucynR5X6wsyKVFWYYZ/MtP7xEtuSKhfnRJtik6GWTi6GzRI2rlglFuhpfCe+bCUo", + "/5uvxmVnKdkNhJ2w0VO1obLwb0RNL96qk+25jwalXHyPxz7Q82Zm1sbhD33VkfLbmNKSl8KIEVkqL6gb", + "+t7EjT1QNsCvrcOCcM1BSksBKP+WQkGmhY/b3wfHPlTYKMZbIUElW15Y4JLlTt+29Vyx9Q/F8qbUBS+G", + "CyQSVtRAJ4Oqq+k59yH7hX3uc6l965eDFqaGXg/3IPQZGEwNkBhS/Zy42/JwjvZtjE2Mc5CZ9zz1S7By", + "kF1vSCVFUef2gg4PRmOQG10CZQ8ridpp8uEqezpCkOt8A7tTqwT55o1+B0OgreRkQQ9K9/U2+V7NbyoG", + "9+JewPuclqvppBKizBLOjoth3dg+xd+w/AYKYm4KH6mc6MtLvkAbe+PN3ix3vk5qVQGH4uEJIefc5oZ4", + "x3a3pVRvcv5A75t/i7MWtS3l7IxqJ9c8HmSPRZblHbmZH2Y/D1NgWN0dp7KDHKhKuk3UrJV0E+lSfTJW", + "Kx+6mvudg1uislDEZJJL67F6gQc9ZjjCTPag5AI6Milxni6iShELybxNtr0ZKo6pcDIESAMfk/TdQOEG", + "jyIg2gs3cgptBTNXu0zMiYTWiXzbIm7Dtr0xjb4/czNLl9/NhYROA17ztZCFF3mYajtlUzljWlK5u02p", + "tUHb4IH1JInlg+FYTSRWu5A2GmuIw7IUmwyZVdbUNo+ptuY91b2MfaOd9jtzqmcQxHVR5QS1HVnSguRC", + "SsjDL+JpexaqlZCQlQLDvGIe6Lk2cvcKc3U4KcWCiCoXBdgeAXEKSs1Vc05RbIIgqiaKAks7mPRpvwno", + "eOSU99Wz2hbnsYvOrC8zEXgKyhXjcRiyLw/h3dPv+ajq/BdztAgxjHXp5l5b6TPseg1HNr1mZekNBqm+", + "1+RnVWM4EibemCmek5VQ2ml2diTVDNWGeH2RC66lKMuuEciKxAtn2X5Nt+d5rl8JcTOj+c1D1CO50M1K", + "i6lPS+0H47UzyV5FppENuq+WETsvzuJP3dFduB3nOLp5bgDm+8Mc67CN+zzWZLy7rn7XfJ6onanFiuVx", + "Gv7Xim5LxqTFWEK01JPtX2WT8/E1ZNTh5dAEMyBLGqIZOI024Dknjqc5py4yD/NflHj745I5uEsicTEN", + "+aSTWrI8KVv1AEBIbcaorqVtehVKPg1XEQubYY4u6T6gI7k4Rv7cDTYzwr0DpeFOQA2iDRsAv7DK/tSW", + "5LKRizOx9c8ftjW7bgX8x/1U3mEeqZCqy5a0pA2q8vU9EhwhXhl4b/wRtnT3N+jhKKSmQeHIGzUAIB2X", + "1IFhVHTSsWDMKSuhyGL9rS4am9A00GxdRku/7SxTjpPntPbtpczYtQRXb8KK1L029RU1pCSa14eWW17A", + "FhQWg7C9tqmyfgbv74DStpXqKd+iykpYQydcyxXBqFG0Y2vw36rmY1IAVOj969ukYnFI4V3eM1S4tWdB", + "JMsY7EYtFxaxdqfIAbNE1Iiy5Zk9JmrsUTIQrVlR0w7+1LEiR9fsZo5yBFUDmTzzetvYaX62I7z1A5z7", + "72OijMfE+3F86GgWFEfdPgZ0MC6xVqlTz+NhiWGFl8ahgbMVjePTknjLN1RFNzxtABySfKvejNwnJniA", + "2G+3kKNU0427uztOCA5GVK96U1IEl80O396Q/FloeC8JJ8eLqRoKkMHutdR4unACO76AjUa5EXuN1Iwt", + "pBz/d/xvSma1H8jo1bajVajBvQTvscOC0o2zwgm0rLnQfHzh1NUT7CvlLIisXtEdERL/MfraP2tasvkO", + "T6gF339G1JIaEnIuQuu7dvGKZuL9gsnUA+btAsJPZdfNxo4ZDLczowRAmyvQGaewMtANhNuAbnnLeXJt", + "WI6qZyumFF52ve0cYsEt3teEWNEi1JGxMl23yauvVWq+/v/brK1wKl9Qqipp7vuXAVF01TOI2x6Fnrj0", + "Elb70/qG6rEngabvYUu00qfzFrcw7h0ZuRGLlU/1e+iAPegHN2h1cadlHNM6us2M3pMQOWop970LY+ND", + "BkCjk9lX9ToAvq3G6CuAfQr8R4tGppYxBvw/C94TbfRCeG3HvE+A5U7KfwRWa1ediW0mYa4OhUJYw6pR", + "hGVbLMAbJxnPJVBlY0MufnIqW1sTkXGjQtroxcb71oxSwJzxllkyXtU6ogFgaUS+CxAWmqcRrQlnT0pK", + "MGLYmpY/rUFKVqQ2zpwO28YrrEnvTfLu24jy39ypwwGYarUfzCSENlMteM1c4LbrjQ0sVJrygsoifJ1x", + "koM09z7Z0J26ve/DQCtrI18c8H7QQJrp5rcHfhAkbQtIuXPuyzt6JhoA6T26KEa4FjCCNeJWsEYRLRKe", + "hCEM8bIKdJuVYoH5ZQkCdMUn0fdjlRXB0WBr5aHj5lHsd9g/DdbddgdfC5x1zBT7z9lPiDpUeH7mTO89", + "adaa1k/4sxGZ9iB4+ueLNizcbs6Q/mM5mleYxNDJ0/TCnU9i8Httw0PsfJDwZHQtuIldRAe5S/ANzbXj", + "+xl1ffCxTFCrw2ao26o9gd+g2iBnmrvAnaHRZ6AUW6RMXR7tkTYha0n290ACPNup1p2t7rRNMIUZ55gm", + "UPszZ7NKVFk+JhrQluYvnEHbQdqFMUEfgbk6se4mcEI1zSo6hU06XSuO7YOV7JpxyC9T5fuU7JRBI8FB", + "u8ZyMUdehkfYmnEwx6MxXkz72Uddg03DJAglEvJaokFzQ3eH+wolSsJe/u38yydPf3365VfEvEAKtgDV", + "lhXu9eVpI8YY79tZPm2M2GB5Or4JPi/dIs57yny6TbMp7qxZbqvamoGDrkTHWEIjF0DkOEb6wdxqr3Cc", + "Nuj7z7VdsUXe+47FUPDH75kUZRkv696IbhFTf2y3AmO/kfgrkIopbRhh11fHdBsrq5ZojsPinmtbZ0Tw", + "3FVfb6iA6UQwTmwhqVBL5GeY9ev8GwS2Vel4lfVJ7FuX04usRQyDMzB+YwakEpUTpdmcxCDC3BIZ5Fw6", + "QyOGdwbRkw2ztXGUMUJ0Mclx0jvnTvMUc7Kf23e7Neo4pzebGBEv/KG8BWmmLOnpjPbbcJLWlP6n4R+R", + "FP174xrNcv8IXhHVD27X+HgUaMN07Qh5IACJPMxOBl3YF72tNCqtVR7t997V2Rc/Xrcu0IMJAwiJ/+AA", + "eGFiZfteE+PuwPnMJTtfN0gJlvI+RQmd5R/K1fSst7lIgi1yRgqtQVm2JIZiYZCIq140+a0JrWSQBotN", + "0I1mWpaR9FlrN8EzFRKOUQnkmpafnmtgd/xzxAcUb9NJM2EOZYhki0p1uwpur+iouYN8yfubmr/BlN2/", + "g9mj6D3nhnLu4sFthlYvbEm98LeCzQImGxzThgM9+YrMXDX9SkLOVN8NvfHCSZMyCJLNXeglbPWBHMVD", + "6/xF6DuQ8dzHjJAfA3eSQLNdC2F7RD8zU0mc3CiVx6hvQBYR/MV4VNh988B1ccfK67crCBKU9jqyIMiw", + "r+jY5dmiF+bSqRUM1zn6tu7gNnJRt2sbW81mdAH36+t3ejamCE282Lr5HKvg3EvV9aNqrv8B9W8sjtwY", + "bt4YxfySqohqq34miu/29qNm5cEAkU4p5Y/TyQI4KKawWPCvrjnEp71LPQQ2J394VC2sdykkYhETWWtn", + "8mCqoEjyiPrI7rNINWTMd8tryfQOG4N6Axr7NVqp5/um6oOrGtL4rtzdp8UNNM2Z2xoRtfK36/eClngf", + "WZcaN7eQKE/It1u6qkpnDiZ/fTD7D3j2l+fF42dP/mP2l8dfPs7h+ZdfP35Mv35On3z97Ak8/cuXzx/D", + "k/lXX8+eFk+fP509f/r8qy+/zp89fzJ7/tXX//HA8CEDsgXU1+4+m/yv7LxciOz8zUV2ZYBtcUIr9gOY", + "vUFdeS6wcZ1Bao4nEVaUlZMz/9P/8CfsJBerdnj/68Q1YJksta7U2enpZrM5CT85XWBSeKZFnS9P/TzY", + "Tqwjr7y5aKLJbdwL7mhrPcZNdaRwjs/efnt5Rc7fXJy0BDM5mzw+eXzyxPWu5bRik7PJM/wJT88S9/3U", + "Edvk7MPH6eR0CbTEGirmjxVoyXL/SAItdu7/akMXC5AnmDBgf1o/PfVixekHlxz/cd+z0zCk4vRDp4ZA", + "ceBLDAc4/eA7WO5/u9O90EViBR+MhGLfa6cz7Fox9lVQwcvppaCyoU4/oLic/P3U2TziD1Ftsefh1Bfa", + "iL/ZwdIHvTWwHvhiy4pgJTnV+bKuTj/gf5B6A6BtEcZTveWn6Dk9/dBZq3s8WGv39/bz8I31ShTggRPz", + "ue3sue/x6Qf7bzARbCuQzIiFtvCJ8xI3h+6imJxNvg1eerGE/GaC3cAwZg9P09PHjyMVaoOviD3cdFZC", + "YU7m88fPR3zAhQ4/cglZww9/5jdcbDjBeoaW09erFZU7lKB0LbkiP/1A2JxAfwqm/AzIXehCoW+onpUs", + "n0wnHfS8/+iQZut3nWL/q12LS//zjufRH4fb3KldlPj51N8tMfbSffND58/uqVLLWhdiE8yCWpk1KQwh", + "Mw9r1f/7dEOZNnKWK5mDDTOHH2ug5amrj937tS1JOXiCdTaDH8Pg9Oivp9ShelIJFSHbt3QTmFLP8WUr", + "jIDS3wjk6hPXUqdXzuV0m80YRwr6MFFNH/FWGLMPh9rc4FYzuilGHXh71jDdHXNupaBFThU2anSl5ieh", + "5KRlDR+jxw6P0+M9a3G3VbCOvbbFTlHQyIq+oQXxqcoZeU1LgxUoyLm78jtLs4f9yaeD7oLbwFlzuK3U", + "83E6+fJT4ueCGwGdlp4dmemffbrpL0GuWQ7kClaVkFSyckd+5k3s760Z6XdInJLmNyicNQRrA1Uk3XTD", + "iWU8FbTbScFnBgPRW7KkvChd8pyosQmroSy0P4vAA2ouIN9JpBISAbAlmqCwPiF1Qi4bjxn6n2zgOjZE", + "WkMpKjQQYeFBOwlFb5q1qIYXQZf/G23THOIF8MyxkWwmip3vay7pRm9tHtyAVzUN6qMP+9JZ7KmTThIv", + "+Ug1/7jV1ELNZ3L2LtB53r3/+N48k2sMqXn3IRDkz05PMXR5KZQ+nXycfugJ+eHD9w3CfEOpSSXZGism", + "I9KEZAvGaZk5AbptOjJ5evJ48vH/BgAA//8trP48ZvUAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index b9fc3b1baa..0ea0473989 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -185,6 +185,15 @@ type Account struct { // Note: the raw account uses `map[int] -> Asset` for this type. CreatedAssets *[]Asset `json:"created-assets,omitempty"` + // IncentiveEligible Whether or not the account can receive block incentives if its balance is in range at proposal time. + IncentiveEligible *bool `json:"incentive-eligible,omitempty"` + + // LastHeartbeat The round in which this account last went online, or explicitly renewed their online status. + LastHeartbeat *uint64 `json:"last-heartbeat,omitempty"` + + // LastProposed The round in which this account last proposed the block. + LastProposed *uint64 `json:"last-proposed,omitempty"` + // MinBalance MicroAlgo balance required by the account. // // The requirement grows based on asset and application usage. diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index b866cc9f42..38fa807a82 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -139,215 +139,217 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "nUw+/v8AAAD//31f+lNw+wAA", + "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", + "AAD//yBzl+wK/QAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 713e5cd7b0..c89afa7516 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -802,211 +802,213 @@ var swaggerSpec = []string{ "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", - "bwAAAP//bsB3VeaoAQA=", + "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", + "9SMOgKoBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index c0e0c71fe2..ba583b859a 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -281,140 +281,142 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 0938833520..8f2add21ae 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,224 +177,225 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/G1EEgEA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 69201ca848..82bbbe6134 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -495,7 +495,7 @@ func (v2 *Handlers) basicAccountInformation(ctx echo.Context, addr basics.Addres } var apiParticipation *model.AccountParticipation - if record.VoteID != (crypto.OneTimeSignatureVerifier{}) { + if !record.VoteID.IsEmpty() { apiParticipation = &model.AccountParticipation{ VoteParticipationKey: record.VoteID[:], SelectionParticipationKey: record.SelectionID[:], @@ -525,6 +525,7 @@ func (v2 *Handlers) basicAccountInformation(ctx echo.Context, addr basics.Addres Status: record.Status.String(), RewardBase: &record.RewardsBase, Participation: apiParticipation, + IncentiveEligible: omitEmpty(record.IncentiveEligible), TotalCreatedAssets: record.TotalAssetParams, TotalCreatedApps: record.TotalAppParams, TotalAssetsOptedIn: record.TotalAssets, @@ -538,6 +539,8 @@ func (v2 *Handlers) basicAccountInformation(ctx echo.Context, addr basics.Addres TotalBoxes: omitEmpty(record.TotalBoxes), TotalBoxBytes: omitEmpty(record.TotalBoxBytes), MinBalance: record.MinBalance(&consensus).Raw, + LastProposed: omitEmpty(uint64(record.LastProposed)), + LastHeartbeat: omitEmpty(uint64(record.LastHeartbeat)), } response := model.AccountResponse(account) return ctx.JSON(http.StatusOK, response) @@ -1401,10 +1404,7 @@ func (v2 *Handlers) getPendingTransactions(ctx echo.Context, max *uint64, format } // MatchAddress uses this to check FeeSink, we don't care about that here. - spec := transactions.SpecialAddresses{ - FeeSink: basics.Address{}, - RewardsPool: basics.Address{}, - } + spec := transactions.SpecialAddresses{} txnLimit := uint64(math.MaxUint64) if max != nil && *max != 0 { diff --git a/data/basics/fraction.go b/data/basics/fraction.go new file mode 100644 index 0000000000..925d342380 --- /dev/null +++ b/data/basics/fraction.go @@ -0,0 +1,69 @@ +// Copyright (C) 2019-2024 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 basics + +import ( + "fmt" +) + +// Fraction represents the mathematical notion of rational number, but is much +// simpler than `big.Rat`. It only supports numerators and denominators of +// uint64. +type Fraction struct { + Numerator uint64 + Denominator uint64 +} + +// NewFraction creates the obvious Fraction, and checks that is not improper, +// nor divides by zero. +func NewFraction(numerator uint64, denominator uint64) Fraction { + if denominator == 0 { + panic("/0") + } + if numerator > denominator { + panic("improper fraction") + } + return Fraction{numerator, denominator} +} + +// NewPercent creates a fraction reflecting the given percentage. +func NewPercent(pct uint64) Fraction { + return NewFraction(pct, 100) +} + +// String returns a string representation of Fraction +func (frac Fraction) String() string { + return fmt.Sprintf("%d/%d", frac.Numerator, frac.Denominator) +} + +// Divvy separates a quantity into two parts according to the fraction. The first +// value is floor(q * frac), the second is q - first. +func (frac Fraction) Divvy(q uint64) (uint64, uint64) { + // can't overflow on proper fractions + first, o := Muldiv(q, frac.Numerator, frac.Denominator) + if o { + panic("overflow") + } + second := q - first + return first, second +} + +// DivvyAlgos is Divvy, but operates on MicroAlgos +func (frac Fraction) DivvyAlgos(q MicroAlgos) (MicroAlgos, MicroAlgos) { + first, second := frac.Divvy(q.Raw) + return MicroAlgos{first}, MicroAlgos{second} +} diff --git a/data/basics/fraction_test.go b/data/basics/fraction_test.go new file mode 100644 index 0000000000..2056717d24 --- /dev/null +++ b/data/basics/fraction_test.go @@ -0,0 +1,81 @@ +// Copyright (C) 2019-2024 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 basics + +import ( + "math" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestFraction(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + third := Fraction{1, 3} + a, b := third.Divvy(6) + require.EqualValues(t, 2, a) + require.EqualValues(t, 4, b) + + a, b = third.Divvy(10) + require.EqualValues(t, 3, a) + require.EqualValues(t, 7, b) +} + +func TestFractionAvoidsOverflow(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + biggestEven := math.MaxUint64 - uint64(1) + + half := Fraction{biggestEven / 2, biggestEven} // should operate as 1/2 even on large numbers + a, b := half.Divvy(6) + require.EqualValues(t, 3, a) + require.EqualValues(t, 3, b) + + a, b = half.Divvy(biggestEven) + require.EqualValues(t, biggestEven/2, a) + require.EqualValues(t, biggestEven/2, b) + + // ensure that overflow is avoided even if reduction isn't possible + uhalf := Fraction{biggestEven / 2, math.MaxUint64} // should be just under half + a, b = uhalf.Divvy(6) + require.EqualValues(t, 2, a) + require.EqualValues(t, 4, b) + + a, b = uhalf.Divvy(biggestEven) + require.EqualValues(t, biggestEven/2-1, a) + require.EqualValues(t, biggestEven/2+1, b) + + // and just to be super careful, ensure that there's also no reduction + // between q and the denominator by using a q that is relatively prime to + // math.MaxUint64 + + // prove 23 is relatively prime to math.MaxUint64 + require.Positive(t, math.MaxUint64%23) + + a, b = uhalf.Divvy(23) + require.EqualValues(t, 11, a) + require.EqualValues(t, 12, b) + + one := Fraction{math.MaxUint64, math.MaxUint64} + a, b = one.Divvy(math.MaxUint64) + require.EqualValues(t, uint64(math.MaxUint64), a) + require.EqualValues(t, 0, b) +} diff --git a/data/basics/msgp_gen.go b/data/basics/msgp_gen.go index 06190153d6..de460960bc 100644 --- a/data/basics/msgp_gen.go +++ b/data/basics/msgp_gen.go @@ -250,8 +250,8 @@ import ( func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0009Len := uint32(19) - var zb0009Mask uint32 /* 20 bits */ + zb0009Len := uint32(22) + var zb0009Mask uint32 /* 23 bits */ if (*z).MicroAlgos.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x2 @@ -280,54 +280,66 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { zb0009Len-- zb0009Mask |= 0x80 } - if (*z).Status == 0 { + if (*z).IncentiveEligible == false { zb0009Len-- zb0009Mask |= 0x100 } - if (*z).SelectionID.MsgIsZero() { + if (*z).LastHeartbeat == 0 { zb0009Len-- zb0009Mask |= 0x200 } - if (*z).AuthAddr.MsgIsZero() { + if (*z).LastProposed == 0 { zb0009Len-- zb0009Mask |= 0x400 } - if (*z).StateProofID.MsgIsZero() { + if (*z).Status == 0 { zb0009Len-- zb0009Mask |= 0x800 } - if (*z).TotalBoxes == 0 { + if (*z).SelectionID.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x1000 } - if (*z).TotalBoxBytes == 0 { + if (*z).AuthAddr.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x2000 } - if (*z).TotalExtraAppPages == 0 { + if (*z).StateProofID.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x4000 } - if ((*z).TotalAppSchema.NumUint == 0) && ((*z).TotalAppSchema.NumByteSlice == 0) { + if (*z).TotalBoxes == 0 { zb0009Len-- zb0009Mask |= 0x8000 } - if (*z).VoteID.MsgIsZero() { + if (*z).TotalBoxBytes == 0 { zb0009Len-- zb0009Mask |= 0x10000 } - if (*z).VoteFirstValid == 0 { + if (*z).TotalExtraAppPages == 0 { zb0009Len-- zb0009Mask |= 0x20000 } - if (*z).VoteKeyDilution == 0 { + if ((*z).TotalAppSchema.NumUint == 0) && ((*z).TotalAppSchema.NumByteSlice == 0) { zb0009Len-- zb0009Mask |= 0x40000 } - if (*z).VoteLastValid == 0 { + if (*z).VoteID.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x80000 } + if (*z).VoteFirstValid == 0 { + zb0009Len-- + zb0009Mask |= 0x100000 + } + if (*z).VoteKeyDilution == 0 { + zb0009Len-- + zb0009Mask |= 0x200000 + } + if (*z).VoteLastValid == 0 { + zb0009Len-- + zb0009Mask |= 0x400000 + } // variable map header, size zb0009Len o = msgp.AppendMapHeader(o, zb0009Len) if zb0009Len != 0 { @@ -451,41 +463,56 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = (*z).RewardedMicroAlgos.MarshalMsg(o) } if (zb0009Mask & 0x100) == 0 { // if not empty + // string "ie" + o = append(o, 0xa2, 0x69, 0x65) + o = msgp.AppendBool(o, (*z).IncentiveEligible) + } + if (zb0009Mask & 0x200) == 0 { // if not empty + // string "lhb" + o = append(o, 0xa3, 0x6c, 0x68, 0x62) + o = msgp.AppendUint64(o, uint64((*z).LastHeartbeat)) + } + if (zb0009Mask & 0x400) == 0 { // if not empty + // string "lpr" + o = append(o, 0xa3, 0x6c, 0x70, 0x72) + o = msgp.AppendUint64(o, uint64((*z).LastProposed)) + } + if (zb0009Mask & 0x800) == 0 { // if not empty // string "onl" o = append(o, 0xa3, 0x6f, 0x6e, 0x6c) o = msgp.AppendByte(o, byte((*z).Status)) } - if (zb0009Mask & 0x200) == 0 { // if not empty + if (zb0009Mask & 0x1000) == 0 { // if not empty // string "sel" o = append(o, 0xa3, 0x73, 0x65, 0x6c) o = (*z).SelectionID.MarshalMsg(o) } - if (zb0009Mask & 0x400) == 0 { // if not empty + if (zb0009Mask & 0x2000) == 0 { // if not empty // string "spend" o = append(o, 0xa5, 0x73, 0x70, 0x65, 0x6e, 0x64) o = (*z).AuthAddr.MarshalMsg(o) } - if (zb0009Mask & 0x800) == 0 { // if not empty + if (zb0009Mask & 0x4000) == 0 { // if not empty // string "stprf" o = append(o, 0xa5, 0x73, 0x74, 0x70, 0x72, 0x66) o = (*z).StateProofID.MarshalMsg(o) } - if (zb0009Mask & 0x1000) == 0 { // if not empty + if (zb0009Mask & 0x8000) == 0 { // if not empty // string "tbx" o = append(o, 0xa3, 0x74, 0x62, 0x78) o = msgp.AppendUint64(o, (*z).TotalBoxes) } - if (zb0009Mask & 0x2000) == 0 { // if not empty + if (zb0009Mask & 0x10000) == 0 { // if not empty // string "tbxb" o = append(o, 0xa4, 0x74, 0x62, 0x78, 0x62) o = msgp.AppendUint64(o, (*z).TotalBoxBytes) } - if (zb0009Mask & 0x4000) == 0 { // if not empty + if (zb0009Mask & 0x20000) == 0 { // if not empty // string "teap" o = append(o, 0xa4, 0x74, 0x65, 0x61, 0x70) o = msgp.AppendUint32(o, (*z).TotalExtraAppPages) } - if (zb0009Mask & 0x8000) == 0 { // if not empty + if (zb0009Mask & 0x40000) == 0 { // if not empty // string "tsch" o = append(o, 0xa4, 0x74, 0x73, 0x63, 0x68) // omitempty: check for empty values @@ -512,22 +539,22 @@ func (z *AccountData) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).TotalAppSchema.NumUint) } } - if (zb0009Mask & 0x10000) == 0 { // if not empty + if (zb0009Mask & 0x80000) == 0 { // if not empty // string "vote" o = append(o, 0xa4, 0x76, 0x6f, 0x74, 0x65) o = (*z).VoteID.MarshalMsg(o) } - if (zb0009Mask & 0x20000) == 0 { // if not empty + if (zb0009Mask & 0x100000) == 0 { // if not empty // string "voteFst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x46, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).VoteFirstValid)) } - if (zb0009Mask & 0x40000) == 0 { // if not empty + if (zb0009Mask & 0x200000) == 0 { // if not empty // string "voteKD" o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x4b, 0x44) o = msgp.AppendUint64(o, (*z).VoteKeyDilution) } - if (zb0009Mask & 0x80000) == 0 { // if not empty + if (zb0009Mask & 0x400000) == 0 { // if not empty // string "voteLst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x4c, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).VoteLastValid)) @@ -653,27 +680,51 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } if zb0009 > 0 { zb0009-- - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadMapHeaderBytes(bts) + { + var zb0014 uint64 + zb0014, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastProposed") + return + } + (*z).LastProposed = Round(zb0014) + } + } + if zb0009 > 0 { + zb0009-- + { + var zb0015 uint64 + zb0015, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastHeartbeat") + return + } + (*z).LastHeartbeat = Round(zb0015) + } + } + if zb0009 > 0 { + zb0009-- + var zb0016 int + var zb0017 bool + zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetParams") return } - if zb0014 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0014), uint64(encodedMaxAssetsPerAccount)) + if zb0016 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0016), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "struct-from-array", "AssetParams") return } - if zb0015 { + if zb0017 { (*z).AssetParams = nil } else if (*z).AssetParams == nil { - (*z).AssetParams = make(map[AssetIndex]AssetParams, zb0014) + (*z).AssetParams = make(map[AssetIndex]AssetParams, zb0016) } - for zb0014 > 0 { + for zb0016 > 0 { var zb0001 AssetIndex var zb0002 AssetParams - zb0014-- + zb0016-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetParams") @@ -689,59 +740,59 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } if zb0009 > 0 { zb0009-- - var zb0016 int - var zb0017 bool - zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0018 int + var zb0019 bool + zb0018, zb0019, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets") return } - if zb0016 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0016), uint64(encodedMaxAssetsPerAccount)) + if zb0018 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0018), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "struct-from-array", "Assets") return } - if zb0017 { + if zb0019 { (*z).Assets = nil } else if (*z).Assets == nil { - (*z).Assets = make(map[AssetIndex]AssetHolding, zb0016) + (*z).Assets = make(map[AssetIndex]AssetHolding, zb0018) } - for zb0016 > 0 { + for zb0018 > 0 { var zb0003 AssetIndex var zb0004 AssetHolding - zb0016-- + zb0018-- bts, err = zb0003.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets") return } - var zb0018 int - var zb0019 bool - zb0018, zb0019, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0020 int + var zb0021 bool + zb0020, zb0021, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003) return } - if zb0018 > 0 { - zb0018-- + if zb0020 > 0 { + zb0020-- zb0004.Amount, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003, "struct-from-array", "Amount") return } } - if zb0018 > 0 { - zb0018-- + if zb0020 > 0 { + zb0020-- zb0004.Frozen, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003, "struct-from-array", "Frozen") return } } - if zb0018 > 0 { - err = msgp.ErrTooManyArrayFields(zb0018) + if zb0020 > 0 { + err = msgp.ErrTooManyArrayFields(zb0020) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003, "struct-from-array") return @@ -752,11 +803,11 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003) return } - if zb0019 { + if zb0021 { zb0004 = AssetHolding{} } - for zb0018 > 0 { - zb0018-- + for zb0020 > 0 { + zb0020-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003) @@ -797,27 +848,35 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } if zb0009 > 0 { zb0009-- - var zb0020 int - var zb0021 bool - zb0020, zb0021, bts, err = msgp.ReadMapHeaderBytes(bts) + (*z).IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "IncentiveEligible") + return + } + } + if zb0009 > 0 { + zb0009-- + var zb0022 int + var zb0023 bool + zb0022, zb0023, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppLocalStates") return } - if zb0020 > EncodedMaxAppLocalStates { - err = msgp.ErrOverflow(uint64(zb0020), uint64(EncodedMaxAppLocalStates)) + if zb0022 > EncodedMaxAppLocalStates { + err = msgp.ErrOverflow(uint64(zb0022), uint64(EncodedMaxAppLocalStates)) err = msgp.WrapError(err, "struct-from-array", "AppLocalStates") return } - if zb0021 { + if zb0023 { (*z).AppLocalStates = nil } else if (*z).AppLocalStates == nil { - (*z).AppLocalStates = make(map[AppIndex]AppLocalState, zb0020) + (*z).AppLocalStates = make(map[AppIndex]AppLocalState, zb0022) } - for zb0020 > 0 { + for zb0022 > 0 { var zb0005 AppIndex var zb0006 AppLocalState - zb0020-- + zb0022-- bts, err = zb0005.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppLocalStates") @@ -833,27 +892,27 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } if zb0009 > 0 { zb0009-- - var zb0022 int - var zb0023 bool - zb0022, zb0023, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0024 int + var zb0025 bool + zb0024, zb0025, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppParams") return } - if zb0022 > EncodedMaxAppParams { - err = msgp.ErrOverflow(uint64(zb0022), uint64(EncodedMaxAppParams)) + if zb0024 > EncodedMaxAppParams { + err = msgp.ErrOverflow(uint64(zb0024), uint64(EncodedMaxAppParams)) err = msgp.WrapError(err, "struct-from-array", "AppParams") return } - if zb0023 { + if zb0025 { (*z).AppParams = nil } else if (*z).AppParams == nil { - (*z).AppParams = make(map[AppIndex]AppParams, zb0022) + (*z).AppParams = make(map[AppIndex]AppParams, zb0024) } - for zb0022 > 0 { + for zb0024 > 0 { var zb0007 AppIndex var zb0008 AppParams - zb0022-- + zb0024-- bts, err = zb0007.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppParams") @@ -869,33 +928,33 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } if zb0009 > 0 { zb0009-- - var zb0024 int - var zb0025 bool - zb0024, zb0025, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0026 int + var zb0027 bool + zb0026, zb0027, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0024, zb0025, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0026, zb0027, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema") return } - if zb0024 > 0 { - zb0024-- + if zb0026 > 0 { + zb0026-- (*z).TotalAppSchema.NumUint, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema", "struct-from-array", "NumUint") return } } - if zb0024 > 0 { - zb0024-- + if zb0026 > 0 { + zb0026-- (*z).TotalAppSchema.NumByteSlice, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema", "struct-from-array", "NumByteSlice") return } } - if zb0024 > 0 { - err = msgp.ErrTooManyArrayFields(zb0024) + if zb0026 > 0 { + err = msgp.ErrTooManyArrayFields(zb0026) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema", "struct-from-array") return @@ -906,11 +965,11 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema") return } - if zb0025 { + if zb0027 { (*z).TotalAppSchema = StateSchema{} } - for zb0024 > 0 { - zb0024-- + for zb0026 > 0 { + zb0026-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema") @@ -988,13 +1047,13 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) switch string(field) { case "onl": { - var zb0026 byte - zb0026, bts, err = msgp.ReadByteBytes(bts) + var zb0028 byte + zb0028, bts, err = msgp.ReadByteBytes(bts) if err != nil { err = msgp.WrapError(err, "Status") return } - (*z).Status = Status(zb0026) + (*z).Status = Status(zb0028) } case "algo": bts, err = (*z).MicroAlgos.UnmarshalMsgWithState(bts, st) @@ -1034,23 +1093,23 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } case "voteFst": { - var zb0027 uint64 - zb0027, bts, err = msgp.ReadUint64Bytes(bts) + var zb0029 uint64 + zb0029, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "VoteFirstValid") return } - (*z).VoteFirstValid = Round(zb0027) + (*z).VoteFirstValid = Round(zb0029) } case "voteLst": { - var zb0028 uint64 - zb0028, bts, err = msgp.ReadUint64Bytes(bts) + var zb0030 uint64 + zb0030, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "VoteLastValid") return } - (*z).VoteLastValid = Round(zb0028) + (*z).VoteLastValid = Round(zb0030) } case "voteKD": (*z).VoteKeyDilution, bts, err = msgp.ReadUint64Bytes(bts) @@ -1058,28 +1117,48 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "VoteKeyDilution") return } + case "lpr": + { + var zb0031 uint64 + zb0031, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LastProposed") + return + } + (*z).LastProposed = Round(zb0031) + } + case "lhb": + { + var zb0032 uint64 + zb0032, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LastHeartbeat") + return + } + (*z).LastHeartbeat = Round(zb0032) + } case "apar": - var zb0029 int - var zb0030 bool - zb0029, zb0030, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0033 int + var zb0034 bool + zb0033, zb0034, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "AssetParams") return } - if zb0029 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0029), uint64(encodedMaxAssetsPerAccount)) + if zb0033 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0033), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "AssetParams") return } - if zb0030 { + if zb0034 { (*z).AssetParams = nil } else if (*z).AssetParams == nil { - (*z).AssetParams = make(map[AssetIndex]AssetParams, zb0029) + (*z).AssetParams = make(map[AssetIndex]AssetParams, zb0033) } - for zb0029 > 0 { + for zb0033 > 0 { var zb0001 AssetIndex var zb0002 AssetParams - zb0029-- + zb0033-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "AssetParams") @@ -1093,59 +1172,59 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (*z).AssetParams[zb0001] = zb0002 } case "asset": - var zb0031 int - var zb0032 bool - zb0031, zb0032, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0035 int + var zb0036 bool + zb0035, zb0036, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Assets") return } - if zb0031 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0031), uint64(encodedMaxAssetsPerAccount)) + if zb0035 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0035), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "Assets") return } - if zb0032 { + if zb0036 { (*z).Assets = nil } else if (*z).Assets == nil { - (*z).Assets = make(map[AssetIndex]AssetHolding, zb0031) + (*z).Assets = make(map[AssetIndex]AssetHolding, zb0035) } - for zb0031 > 0 { + for zb0035 > 0 { var zb0003 AssetIndex var zb0004 AssetHolding - zb0031-- + zb0035-- bts, err = zb0003.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "Assets") return } - var zb0033 int - var zb0034 bool - zb0033, zb0034, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0037 int + var zb0038 bool + zb0037, zb0038, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0033, zb0034, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0037, zb0038, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003) return } - if zb0033 > 0 { - zb0033-- + if zb0037 > 0 { + zb0037-- zb0004.Amount, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003, "struct-from-array", "Amount") return } } - if zb0033 > 0 { - zb0033-- + if zb0037 > 0 { + zb0037-- zb0004.Frozen, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003, "struct-from-array", "Frozen") return } } - if zb0033 > 0 { - err = msgp.ErrTooManyArrayFields(zb0033) + if zb0037 > 0 { + err = msgp.ErrTooManyArrayFields(zb0037) if err != nil { err = msgp.WrapError(err, "Assets", zb0003, "struct-from-array") return @@ -1156,11 +1235,11 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "Assets", zb0003) return } - if zb0034 { + if zb0038 { zb0004 = AssetHolding{} } - for zb0033 > 0 { - zb0033-- + for zb0037 > 0 { + zb0037-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003) @@ -1196,28 +1275,34 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "AuthAddr") return } + case "ie": + (*z).IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "IncentiveEligible") + return + } case "appl": - var zb0035 int - var zb0036 bool - zb0035, zb0036, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0039 int + var zb0040 bool + zb0039, zb0040, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "AppLocalStates") return } - if zb0035 > EncodedMaxAppLocalStates { - err = msgp.ErrOverflow(uint64(zb0035), uint64(EncodedMaxAppLocalStates)) + if zb0039 > EncodedMaxAppLocalStates { + err = msgp.ErrOverflow(uint64(zb0039), uint64(EncodedMaxAppLocalStates)) err = msgp.WrapError(err, "AppLocalStates") return } - if zb0036 { + if zb0040 { (*z).AppLocalStates = nil } else if (*z).AppLocalStates == nil { - (*z).AppLocalStates = make(map[AppIndex]AppLocalState, zb0035) + (*z).AppLocalStates = make(map[AppIndex]AppLocalState, zb0039) } - for zb0035 > 0 { + for zb0039 > 0 { var zb0005 AppIndex var zb0006 AppLocalState - zb0035-- + zb0039-- bts, err = zb0005.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "AppLocalStates") @@ -1231,27 +1316,27 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (*z).AppLocalStates[zb0005] = zb0006 } case "appp": - var zb0037 int - var zb0038 bool - zb0037, zb0038, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0041 int + var zb0042 bool + zb0041, zb0042, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "AppParams") return } - if zb0037 > EncodedMaxAppParams { - err = msgp.ErrOverflow(uint64(zb0037), uint64(EncodedMaxAppParams)) + if zb0041 > EncodedMaxAppParams { + err = msgp.ErrOverflow(uint64(zb0041), uint64(EncodedMaxAppParams)) err = msgp.WrapError(err, "AppParams") return } - if zb0038 { + if zb0042 { (*z).AppParams = nil } else if (*z).AppParams == nil { - (*z).AppParams = make(map[AppIndex]AppParams, zb0037) + (*z).AppParams = make(map[AppIndex]AppParams, zb0041) } - for zb0037 > 0 { + for zb0041 > 0 { var zb0007 AppIndex var zb0008 AppParams - zb0037-- + zb0041-- bts, err = zb0007.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "AppParams") @@ -1265,33 +1350,33 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (*z).AppParams[zb0007] = zb0008 } case "tsch": - var zb0039 int - var zb0040 bool - zb0039, zb0040, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0043 int + var zb0044 bool + zb0043, zb0044, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0039, zb0040, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0043, zb0044, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema") return } - if zb0039 > 0 { - zb0039-- + if zb0043 > 0 { + zb0043-- (*z).TotalAppSchema.NumUint, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema", "struct-from-array", "NumUint") return } } - if zb0039 > 0 { - zb0039-- + if zb0043 > 0 { + zb0043-- (*z).TotalAppSchema.NumByteSlice, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema", "struct-from-array", "NumByteSlice") return } } - if zb0039 > 0 { - err = msgp.ErrTooManyArrayFields(zb0039) + if zb0043 > 0 { + err = msgp.ErrTooManyArrayFields(zb0043) if err != nil { err = msgp.WrapError(err, "TotalAppSchema", "struct-from-array") return @@ -1302,11 +1387,11 @@ func (z *AccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "TotalAppSchema") return } - if zb0040 { + if zb0044 { (*z).TotalAppSchema = StateSchema{} } - for zb0039 > 0 { - zb0039-- + for zb0043 > 0 { + zb0043-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema") @@ -1375,7 +1460,7 @@ func (_ *AccountData) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *AccountData) Msgsize() (s int) { - s = 3 + 4 + msgp.ByteSize + 5 + (*z).MicroAlgos.Msgsize() + 6 + msgp.Uint64Size + 4 + (*z).RewardedMicroAlgos.Msgsize() + 5 + (*z).VoteID.Msgsize() + 4 + (*z).SelectionID.Msgsize() + 6 + (*z).StateProofID.Msgsize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 5 + msgp.MapHeaderSize + s = 3 + 4 + msgp.ByteSize + 5 + (*z).MicroAlgos.Msgsize() + 6 + msgp.Uint64Size + 4 + (*z).RewardedMicroAlgos.Msgsize() + 5 + (*z).VoteID.Msgsize() + 4 + (*z).SelectionID.Msgsize() + 6 + (*z).StateProofID.Msgsize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + msgp.MapHeaderSize if (*z).AssetParams != nil { for zb0001, zb0002 := range (*z).AssetParams { _ = zb0001 @@ -1391,7 +1476,7 @@ func (z *AccountData) Msgsize() (s int) { s += 0 + zb0003.Msgsize() + 1 + 2 + msgp.Uint64Size + 2 + msgp.BoolSize } } - s += 6 + (*z).AuthAddr.Msgsize() + 5 + msgp.MapHeaderSize + s += 6 + (*z).AuthAddr.Msgsize() + 3 + msgp.BoolSize + 5 + msgp.MapHeaderSize if (*z).AppLocalStates != nil { for zb0005, zb0006 := range (*z).AppLocalStates { _ = zb0005 @@ -1413,12 +1498,12 @@ func (z *AccountData) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *AccountData) MsgIsZero() bool { - return ((*z).Status == 0) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) && ((*z).RewardedMicroAlgos.MsgIsZero()) && ((*z).VoteID.MsgIsZero()) && ((*z).SelectionID.MsgIsZero()) && ((*z).StateProofID.MsgIsZero()) && ((*z).VoteFirstValid == 0) && ((*z).VoteLastValid == 0) && ((*z).VoteKeyDilution == 0) && (len((*z).AssetParams) == 0) && (len((*z).Assets) == 0) && ((*z).AuthAddr.MsgIsZero()) && (len((*z).AppLocalStates) == 0) && (len((*z).AppParams) == 0) && (((*z).TotalAppSchema.NumUint == 0) && ((*z).TotalAppSchema.NumByteSlice == 0)) && ((*z).TotalExtraAppPages == 0) && ((*z).TotalBoxes == 0) && ((*z).TotalBoxBytes == 0) + return ((*z).Status == 0) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) && ((*z).RewardedMicroAlgos.MsgIsZero()) && ((*z).VoteID.MsgIsZero()) && ((*z).SelectionID.MsgIsZero()) && ((*z).StateProofID.MsgIsZero()) && ((*z).VoteFirstValid == 0) && ((*z).VoteLastValid == 0) && ((*z).VoteKeyDilution == 0) && ((*z).LastProposed == 0) && ((*z).LastHeartbeat == 0) && (len((*z).AssetParams) == 0) && (len((*z).Assets) == 0) && ((*z).AuthAddr.MsgIsZero()) && ((*z).IncentiveEligible == false) && (len((*z).AppLocalStates) == 0) && (len((*z).AppParams) == 0) && (((*z).TotalAppSchema.NumUint == 0) && ((*z).TotalAppSchema.NumByteSlice == 0)) && ((*z).TotalExtraAppPages == 0) && ((*z).TotalBoxes == 0) && ((*z).TotalBoxBytes == 0) } // MaxSize returns a maximum valid message size for this message type func AccountDataMaxSize() (s int) { - s = 3 + 4 + msgp.ByteSize + 5 + MicroAlgosMaxSize() + 6 + msgp.Uint64Size + 4 + MicroAlgosMaxSize() + 5 + crypto.OneTimeSignatureVerifierMaxSize() + 4 + crypto.VRFVerifierMaxSize() + 6 + merklesignature.CommitmentMaxSize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 5 + s = 3 + 4 + msgp.ByteSize + 5 + MicroAlgosMaxSize() + 6 + msgp.Uint64Size + 4 + MicroAlgosMaxSize() + 5 + crypto.OneTimeSignatureVerifierMaxSize() + 4 + crypto.VRFVerifierMaxSize() + 6 + merklesignature.CommitmentMaxSize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 s += msgp.MapHeaderSize // Adding size of map keys for z.AssetParams s += encodedMaxAssetsPerAccount * (AssetIndexMaxSize()) @@ -1431,7 +1516,7 @@ func AccountDataMaxSize() (s int) { // Adding size of map values for z.Assets s += encodedMaxAssetsPerAccount * (1) s += 2 + msgp.Uint64Size + 2 + msgp.BoolSize - s += 6 + AddressMaxSize() + 5 + s += 6 + AddressMaxSize() + 3 + msgp.BoolSize + 5 s += msgp.MapHeaderSize // Adding size of map keys for z.AppLocalStates s += EncodedMaxAppLocalStates * (AppIndexMaxSize()) @@ -3199,8 +3284,8 @@ func AssetParamsMaxSize() (s int) { func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0009Len := uint32(20) - var zb0009Mask uint32 /* 22 bits */ + zb0009Len := uint32(23) + var zb0009Mask uint32 /* 25 bits */ if (*z).Addr.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x4 @@ -3233,54 +3318,66 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { zb0009Len-- zb0009Mask |= 0x200 } - if (*z).AccountData.Status == 0 { + if (*z).AccountData.IncentiveEligible == false { zb0009Len-- zb0009Mask |= 0x400 } - if (*z).AccountData.SelectionID.MsgIsZero() { + if (*z).AccountData.LastHeartbeat == 0 { zb0009Len-- zb0009Mask |= 0x800 } - if (*z).AccountData.AuthAddr.MsgIsZero() { + if (*z).AccountData.LastProposed == 0 { zb0009Len-- zb0009Mask |= 0x1000 } - if (*z).AccountData.StateProofID.MsgIsZero() { + if (*z).AccountData.Status == 0 { zb0009Len-- zb0009Mask |= 0x2000 } - if (*z).AccountData.TotalBoxes == 0 { + if (*z).AccountData.SelectionID.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x4000 } - if (*z).AccountData.TotalBoxBytes == 0 { + if (*z).AccountData.AuthAddr.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x8000 } - if (*z).AccountData.TotalExtraAppPages == 0 { + if (*z).AccountData.StateProofID.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x10000 } - if ((*z).AccountData.TotalAppSchema.NumUint == 0) && ((*z).AccountData.TotalAppSchema.NumByteSlice == 0) { + if (*z).AccountData.TotalBoxes == 0 { zb0009Len-- zb0009Mask |= 0x20000 } - if (*z).AccountData.VoteID.MsgIsZero() { + if (*z).AccountData.TotalBoxBytes == 0 { zb0009Len-- zb0009Mask |= 0x40000 } - if (*z).AccountData.VoteFirstValid == 0 { + if (*z).AccountData.TotalExtraAppPages == 0 { zb0009Len-- zb0009Mask |= 0x80000 } - if (*z).AccountData.VoteKeyDilution == 0 { + if ((*z).AccountData.TotalAppSchema.NumUint == 0) && ((*z).AccountData.TotalAppSchema.NumByteSlice == 0) { zb0009Len-- zb0009Mask |= 0x100000 } - if (*z).AccountData.VoteLastValid == 0 { + if (*z).AccountData.VoteID.MsgIsZero() { zb0009Len-- zb0009Mask |= 0x200000 } + if (*z).AccountData.VoteFirstValid == 0 { + zb0009Len-- + zb0009Mask |= 0x400000 + } + if (*z).AccountData.VoteKeyDilution == 0 { + zb0009Len-- + zb0009Mask |= 0x800000 + } + if (*z).AccountData.VoteLastValid == 0 { + zb0009Len-- + zb0009Mask |= 0x1000000 + } // variable map header, size zb0009Len o = msgp.AppendMapHeader(o, zb0009Len) if zb0009Len != 0 { @@ -3409,41 +3506,56 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = (*z).AccountData.RewardedMicroAlgos.MarshalMsg(o) } if (zb0009Mask & 0x400) == 0 { // if not empty + // string "ie" + o = append(o, 0xa2, 0x69, 0x65) + o = msgp.AppendBool(o, (*z).AccountData.IncentiveEligible) + } + if (zb0009Mask & 0x800) == 0 { // if not empty + // string "lhb" + o = append(o, 0xa3, 0x6c, 0x68, 0x62) + o = msgp.AppendUint64(o, uint64((*z).AccountData.LastHeartbeat)) + } + if (zb0009Mask & 0x1000) == 0 { // if not empty + // string "lpr" + o = append(o, 0xa3, 0x6c, 0x70, 0x72) + o = msgp.AppendUint64(o, uint64((*z).AccountData.LastProposed)) + } + if (zb0009Mask & 0x2000) == 0 { // if not empty // string "onl" o = append(o, 0xa3, 0x6f, 0x6e, 0x6c) o = msgp.AppendByte(o, byte((*z).AccountData.Status)) } - if (zb0009Mask & 0x800) == 0 { // if not empty + if (zb0009Mask & 0x4000) == 0 { // if not empty // string "sel" o = append(o, 0xa3, 0x73, 0x65, 0x6c) o = (*z).AccountData.SelectionID.MarshalMsg(o) } - if (zb0009Mask & 0x1000) == 0 { // if not empty + if (zb0009Mask & 0x8000) == 0 { // if not empty // string "spend" o = append(o, 0xa5, 0x73, 0x70, 0x65, 0x6e, 0x64) o = (*z).AccountData.AuthAddr.MarshalMsg(o) } - if (zb0009Mask & 0x2000) == 0 { // if not empty + if (zb0009Mask & 0x10000) == 0 { // if not empty // string "stprf" o = append(o, 0xa5, 0x73, 0x74, 0x70, 0x72, 0x66) o = (*z).AccountData.StateProofID.MarshalMsg(o) } - if (zb0009Mask & 0x4000) == 0 { // if not empty + if (zb0009Mask & 0x20000) == 0 { // if not empty // string "tbx" o = append(o, 0xa3, 0x74, 0x62, 0x78) o = msgp.AppendUint64(o, (*z).AccountData.TotalBoxes) } - if (zb0009Mask & 0x8000) == 0 { // if not empty + if (zb0009Mask & 0x40000) == 0 { // if not empty // string "tbxb" o = append(o, 0xa4, 0x74, 0x62, 0x78, 0x62) o = msgp.AppendUint64(o, (*z).AccountData.TotalBoxBytes) } - if (zb0009Mask & 0x10000) == 0 { // if not empty + if (zb0009Mask & 0x80000) == 0 { // if not empty // string "teap" o = append(o, 0xa4, 0x74, 0x65, 0x61, 0x70) o = msgp.AppendUint32(o, (*z).AccountData.TotalExtraAppPages) } - if (zb0009Mask & 0x20000) == 0 { // if not empty + if (zb0009Mask & 0x100000) == 0 { // if not empty // string "tsch" o = append(o, 0xa4, 0x74, 0x73, 0x63, 0x68) // omitempty: check for empty values @@ -3470,22 +3582,22 @@ func (z *BalanceRecord) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).AccountData.TotalAppSchema.NumUint) } } - if (zb0009Mask & 0x40000) == 0 { // if not empty + if (zb0009Mask & 0x200000) == 0 { // if not empty // string "vote" o = append(o, 0xa4, 0x76, 0x6f, 0x74, 0x65) o = (*z).AccountData.VoteID.MarshalMsg(o) } - if (zb0009Mask & 0x80000) == 0 { // if not empty + if (zb0009Mask & 0x400000) == 0 { // if not empty // string "voteFst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x46, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).AccountData.VoteFirstValid)) } - if (zb0009Mask & 0x100000) == 0 { // if not empty + if (zb0009Mask & 0x800000) == 0 { // if not empty // string "voteKD" o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x4b, 0x44) o = msgp.AppendUint64(o, (*z).AccountData.VoteKeyDilution) } - if (zb0009Mask & 0x200000) == 0 { // if not empty + if (zb0009Mask & 0x1000000) == 0 { // if not empty // string "voteLst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x4c, 0x73, 0x74) o = msgp.AppendUint64(o, uint64((*z).AccountData.VoteLastValid)) @@ -3619,27 +3731,51 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState } if zb0009 > 0 { zb0009-- - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadMapHeaderBytes(bts) + { + var zb0014 uint64 + zb0014, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastProposed") + return + } + (*z).AccountData.LastProposed = Round(zb0014) + } + } + if zb0009 > 0 { + zb0009-- + { + var zb0015 uint64 + zb0015, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastHeartbeat") + return + } + (*z).AccountData.LastHeartbeat = Round(zb0015) + } + } + if zb0009 > 0 { + zb0009-- + var zb0016 int + var zb0017 bool + zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetParams") return } - if zb0014 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0014), uint64(encodedMaxAssetsPerAccount)) + if zb0016 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0016), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "struct-from-array", "AssetParams") return } - if zb0015 { + if zb0017 { (*z).AccountData.AssetParams = nil } else if (*z).AccountData.AssetParams == nil { - (*z).AccountData.AssetParams = make(map[AssetIndex]AssetParams, zb0014) + (*z).AccountData.AssetParams = make(map[AssetIndex]AssetParams, zb0016) } - for zb0014 > 0 { + for zb0016 > 0 { var zb0001 AssetIndex var zb0002 AssetParams - zb0014-- + zb0016-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AssetParams") @@ -3655,59 +3791,59 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState } if zb0009 > 0 { zb0009-- - var zb0016 int - var zb0017 bool - zb0016, zb0017, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0018 int + var zb0019 bool + zb0018, zb0019, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets") return } - if zb0016 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0016), uint64(encodedMaxAssetsPerAccount)) + if zb0018 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0018), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "struct-from-array", "Assets") return } - if zb0017 { + if zb0019 { (*z).AccountData.Assets = nil } else if (*z).AccountData.Assets == nil { - (*z).AccountData.Assets = make(map[AssetIndex]AssetHolding, zb0016) + (*z).AccountData.Assets = make(map[AssetIndex]AssetHolding, zb0018) } - for zb0016 > 0 { + for zb0018 > 0 { var zb0003 AssetIndex var zb0004 AssetHolding - zb0016-- + zb0018-- bts, err = zb0003.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets") return } - var zb0018 int - var zb0019 bool - zb0018, zb0019, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0020 int + var zb0021 bool + zb0020, zb0021, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0020, zb0021, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003) return } - if zb0018 > 0 { - zb0018-- + if zb0020 > 0 { + zb0020-- zb0004.Amount, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003, "struct-from-array", "Amount") return } } - if zb0018 > 0 { - zb0018-- + if zb0020 > 0 { + zb0020-- zb0004.Frozen, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003, "struct-from-array", "Frozen") return } } - if zb0018 > 0 { - err = msgp.ErrTooManyArrayFields(zb0018) + if zb0020 > 0 { + err = msgp.ErrTooManyArrayFields(zb0020) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003, "struct-from-array") return @@ -3718,11 +3854,11 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003) return } - if zb0019 { + if zb0021 { zb0004 = AssetHolding{} } - for zb0018 > 0 { - zb0018-- + for zb0020 > 0 { + zb0020-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Assets", zb0003) @@ -3763,27 +3899,35 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState } if zb0009 > 0 { zb0009-- - var zb0020 int - var zb0021 bool - zb0020, zb0021, bts, err = msgp.ReadMapHeaderBytes(bts) + (*z).AccountData.IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "IncentiveEligible") + return + } + } + if zb0009 > 0 { + zb0009-- + var zb0022 int + var zb0023 bool + zb0022, zb0023, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppLocalStates") return } - if zb0020 > EncodedMaxAppLocalStates { - err = msgp.ErrOverflow(uint64(zb0020), uint64(EncodedMaxAppLocalStates)) + if zb0022 > EncodedMaxAppLocalStates { + err = msgp.ErrOverflow(uint64(zb0022), uint64(EncodedMaxAppLocalStates)) err = msgp.WrapError(err, "struct-from-array", "AppLocalStates") return } - if zb0021 { + if zb0023 { (*z).AccountData.AppLocalStates = nil } else if (*z).AccountData.AppLocalStates == nil { - (*z).AccountData.AppLocalStates = make(map[AppIndex]AppLocalState, zb0020) + (*z).AccountData.AppLocalStates = make(map[AppIndex]AppLocalState, zb0022) } - for zb0020 > 0 { + for zb0022 > 0 { var zb0005 AppIndex var zb0006 AppLocalState - zb0020-- + zb0022-- bts, err = zb0005.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppLocalStates") @@ -3799,27 +3943,27 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState } if zb0009 > 0 { zb0009-- - var zb0022 int - var zb0023 bool - zb0022, zb0023, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0024 int + var zb0025 bool + zb0024, zb0025, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppParams") return } - if zb0022 > EncodedMaxAppParams { - err = msgp.ErrOverflow(uint64(zb0022), uint64(EncodedMaxAppParams)) + if zb0024 > EncodedMaxAppParams { + err = msgp.ErrOverflow(uint64(zb0024), uint64(EncodedMaxAppParams)) err = msgp.WrapError(err, "struct-from-array", "AppParams") return } - if zb0023 { + if zb0025 { (*z).AccountData.AppParams = nil } else if (*z).AccountData.AppParams == nil { - (*z).AccountData.AppParams = make(map[AppIndex]AppParams, zb0022) + (*z).AccountData.AppParams = make(map[AppIndex]AppParams, zb0024) } - for zb0022 > 0 { + for zb0024 > 0 { var zb0007 AppIndex var zb0008 AppParams - zb0022-- + zb0024-- bts, err = zb0007.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "AppParams") @@ -3835,33 +3979,33 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState } if zb0009 > 0 { zb0009-- - var zb0024 int - var zb0025 bool - zb0024, zb0025, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0026 int + var zb0027 bool + zb0026, zb0027, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0024, zb0025, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0026, zb0027, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema") return } - if zb0024 > 0 { - zb0024-- + if zb0026 > 0 { + zb0026-- (*z).AccountData.TotalAppSchema.NumUint, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema", "struct-from-array", "NumUint") return } } - if zb0024 > 0 { - zb0024-- + if zb0026 > 0 { + zb0026-- (*z).AccountData.TotalAppSchema.NumByteSlice, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema", "struct-from-array", "NumByteSlice") return } } - if zb0024 > 0 { - err = msgp.ErrTooManyArrayFields(zb0024) + if zb0026 > 0 { + err = msgp.ErrTooManyArrayFields(zb0026) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema", "struct-from-array") return @@ -3872,11 +4016,11 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema") return } - if zb0025 { + if zb0027 { (*z).AccountData.TotalAppSchema = StateSchema{} } - for zb0024 > 0 { - zb0024-- + for zb0026 > 0 { + zb0026-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TotalAppSchema") @@ -3960,13 +4104,13 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState } case "onl": { - var zb0026 byte - zb0026, bts, err = msgp.ReadByteBytes(bts) + var zb0028 byte + zb0028, bts, err = msgp.ReadByteBytes(bts) if err != nil { err = msgp.WrapError(err, "Status") return } - (*z).AccountData.Status = Status(zb0026) + (*z).AccountData.Status = Status(zb0028) } case "algo": bts, err = (*z).AccountData.MicroAlgos.UnmarshalMsgWithState(bts, st) @@ -4006,23 +4150,23 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState } case "voteFst": { - var zb0027 uint64 - zb0027, bts, err = msgp.ReadUint64Bytes(bts) + var zb0029 uint64 + zb0029, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "VoteFirstValid") return } - (*z).AccountData.VoteFirstValid = Round(zb0027) + (*z).AccountData.VoteFirstValid = Round(zb0029) } case "voteLst": { - var zb0028 uint64 - zb0028, bts, err = msgp.ReadUint64Bytes(bts) + var zb0030 uint64 + zb0030, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "VoteLastValid") return } - (*z).AccountData.VoteLastValid = Round(zb0028) + (*z).AccountData.VoteLastValid = Round(zb0030) } case "voteKD": (*z).AccountData.VoteKeyDilution, bts, err = msgp.ReadUint64Bytes(bts) @@ -4030,28 +4174,48 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState err = msgp.WrapError(err, "VoteKeyDilution") return } + case "lpr": + { + var zb0031 uint64 + zb0031, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LastProposed") + return + } + (*z).AccountData.LastProposed = Round(zb0031) + } + case "lhb": + { + var zb0032 uint64 + zb0032, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LastHeartbeat") + return + } + (*z).AccountData.LastHeartbeat = Round(zb0032) + } case "apar": - var zb0029 int - var zb0030 bool - zb0029, zb0030, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0033 int + var zb0034 bool + zb0033, zb0034, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "AssetParams") return } - if zb0029 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0029), uint64(encodedMaxAssetsPerAccount)) + if zb0033 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0033), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "AssetParams") return } - if zb0030 { + if zb0034 { (*z).AccountData.AssetParams = nil } else if (*z).AccountData.AssetParams == nil { - (*z).AccountData.AssetParams = make(map[AssetIndex]AssetParams, zb0029) + (*z).AccountData.AssetParams = make(map[AssetIndex]AssetParams, zb0033) } - for zb0029 > 0 { + for zb0033 > 0 { var zb0001 AssetIndex var zb0002 AssetParams - zb0029-- + zb0033-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "AssetParams") @@ -4065,59 +4229,59 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState (*z).AccountData.AssetParams[zb0001] = zb0002 } case "asset": - var zb0031 int - var zb0032 bool - zb0031, zb0032, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0035 int + var zb0036 bool + zb0035, zb0036, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Assets") return } - if zb0031 > encodedMaxAssetsPerAccount { - err = msgp.ErrOverflow(uint64(zb0031), uint64(encodedMaxAssetsPerAccount)) + if zb0035 > encodedMaxAssetsPerAccount { + err = msgp.ErrOverflow(uint64(zb0035), uint64(encodedMaxAssetsPerAccount)) err = msgp.WrapError(err, "Assets") return } - if zb0032 { + if zb0036 { (*z).AccountData.Assets = nil } else if (*z).AccountData.Assets == nil { - (*z).AccountData.Assets = make(map[AssetIndex]AssetHolding, zb0031) + (*z).AccountData.Assets = make(map[AssetIndex]AssetHolding, zb0035) } - for zb0031 > 0 { + for zb0035 > 0 { var zb0003 AssetIndex var zb0004 AssetHolding - zb0031-- + zb0035-- bts, err = zb0003.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "Assets") return } - var zb0033 int - var zb0034 bool - zb0033, zb0034, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0037 int + var zb0038 bool + zb0037, zb0038, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0033, zb0034, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0037, zb0038, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003) return } - if zb0033 > 0 { - zb0033-- + if zb0037 > 0 { + zb0037-- zb0004.Amount, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003, "struct-from-array", "Amount") return } } - if zb0033 > 0 { - zb0033-- + if zb0037 > 0 { + zb0037-- zb0004.Frozen, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003, "struct-from-array", "Frozen") return } } - if zb0033 > 0 { - err = msgp.ErrTooManyArrayFields(zb0033) + if zb0037 > 0 { + err = msgp.ErrTooManyArrayFields(zb0037) if err != nil { err = msgp.WrapError(err, "Assets", zb0003, "struct-from-array") return @@ -4128,11 +4292,11 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState err = msgp.WrapError(err, "Assets", zb0003) return } - if zb0034 { + if zb0038 { zb0004 = AssetHolding{} } - for zb0033 > 0 { - zb0033-- + for zb0037 > 0 { + zb0037-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "Assets", zb0003) @@ -4168,28 +4332,34 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState err = msgp.WrapError(err, "AuthAddr") return } + case "ie": + (*z).AccountData.IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "IncentiveEligible") + return + } case "appl": - var zb0035 int - var zb0036 bool - zb0035, zb0036, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0039 int + var zb0040 bool + zb0039, zb0040, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "AppLocalStates") return } - if zb0035 > EncodedMaxAppLocalStates { - err = msgp.ErrOverflow(uint64(zb0035), uint64(EncodedMaxAppLocalStates)) + if zb0039 > EncodedMaxAppLocalStates { + err = msgp.ErrOverflow(uint64(zb0039), uint64(EncodedMaxAppLocalStates)) err = msgp.WrapError(err, "AppLocalStates") return } - if zb0036 { + if zb0040 { (*z).AccountData.AppLocalStates = nil } else if (*z).AccountData.AppLocalStates == nil { - (*z).AccountData.AppLocalStates = make(map[AppIndex]AppLocalState, zb0035) + (*z).AccountData.AppLocalStates = make(map[AppIndex]AppLocalState, zb0039) } - for zb0035 > 0 { + for zb0039 > 0 { var zb0005 AppIndex var zb0006 AppLocalState - zb0035-- + zb0039-- bts, err = zb0005.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "AppLocalStates") @@ -4203,27 +4373,27 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState (*z).AccountData.AppLocalStates[zb0005] = zb0006 } case "appp": - var zb0037 int - var zb0038 bool - zb0037, zb0038, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0041 int + var zb0042 bool + zb0041, zb0042, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "AppParams") return } - if zb0037 > EncodedMaxAppParams { - err = msgp.ErrOverflow(uint64(zb0037), uint64(EncodedMaxAppParams)) + if zb0041 > EncodedMaxAppParams { + err = msgp.ErrOverflow(uint64(zb0041), uint64(EncodedMaxAppParams)) err = msgp.WrapError(err, "AppParams") return } - if zb0038 { + if zb0042 { (*z).AccountData.AppParams = nil } else if (*z).AccountData.AppParams == nil { - (*z).AccountData.AppParams = make(map[AppIndex]AppParams, zb0037) + (*z).AccountData.AppParams = make(map[AppIndex]AppParams, zb0041) } - for zb0037 > 0 { + for zb0041 > 0 { var zb0007 AppIndex var zb0008 AppParams - zb0037-- + zb0041-- bts, err = zb0007.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "AppParams") @@ -4237,33 +4407,33 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState (*z).AccountData.AppParams[zb0007] = zb0008 } case "tsch": - var zb0039 int - var zb0040 bool - zb0039, zb0040, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0043 int + var zb0044 bool + zb0043, zb0044, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0039, zb0040, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0043, zb0044, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema") return } - if zb0039 > 0 { - zb0039-- + if zb0043 > 0 { + zb0043-- (*z).AccountData.TotalAppSchema.NumUint, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema", "struct-from-array", "NumUint") return } } - if zb0039 > 0 { - zb0039-- + if zb0043 > 0 { + zb0043-- (*z).AccountData.TotalAppSchema.NumByteSlice, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema", "struct-from-array", "NumByteSlice") return } } - if zb0039 > 0 { - err = msgp.ErrTooManyArrayFields(zb0039) + if zb0043 > 0 { + err = msgp.ErrTooManyArrayFields(zb0043) if err != nil { err = msgp.WrapError(err, "TotalAppSchema", "struct-from-array") return @@ -4274,11 +4444,11 @@ func (z *BalanceRecord) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState err = msgp.WrapError(err, "TotalAppSchema") return } - if zb0040 { + if zb0044 { (*z).AccountData.TotalAppSchema = StateSchema{} } - for zb0039 > 0 { - zb0039-- + for zb0043 > 0 { + zb0043-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err, "TotalAppSchema") @@ -4347,7 +4517,7 @@ func (_ *BalanceRecord) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *BalanceRecord) Msgsize() (s int) { - s = 3 + 5 + (*z).Addr.Msgsize() + 4 + msgp.ByteSize + 5 + (*z).AccountData.MicroAlgos.Msgsize() + 6 + msgp.Uint64Size + 4 + (*z).AccountData.RewardedMicroAlgos.Msgsize() + 5 + (*z).AccountData.VoteID.Msgsize() + 4 + (*z).AccountData.SelectionID.Msgsize() + 6 + (*z).AccountData.StateProofID.Msgsize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 5 + msgp.MapHeaderSize + s = 3 + 5 + (*z).Addr.Msgsize() + 4 + msgp.ByteSize + 5 + (*z).AccountData.MicroAlgos.Msgsize() + 6 + msgp.Uint64Size + 4 + (*z).AccountData.RewardedMicroAlgos.Msgsize() + 5 + (*z).AccountData.VoteID.Msgsize() + 4 + (*z).AccountData.SelectionID.Msgsize() + 6 + (*z).AccountData.StateProofID.Msgsize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + msgp.MapHeaderSize if (*z).AccountData.AssetParams != nil { for zb0001, zb0002 := range (*z).AccountData.AssetParams { _ = zb0001 @@ -4363,7 +4533,7 @@ func (z *BalanceRecord) Msgsize() (s int) { s += 0 + zb0003.Msgsize() + 1 + 2 + msgp.Uint64Size + 2 + msgp.BoolSize } } - s += 6 + (*z).AccountData.AuthAddr.Msgsize() + 5 + msgp.MapHeaderSize + s += 6 + (*z).AccountData.AuthAddr.Msgsize() + 3 + msgp.BoolSize + 5 + msgp.MapHeaderSize if (*z).AccountData.AppLocalStates != nil { for zb0005, zb0006 := range (*z).AccountData.AppLocalStates { _ = zb0005 @@ -4385,12 +4555,12 @@ func (z *BalanceRecord) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *BalanceRecord) MsgIsZero() bool { - return ((*z).Addr.MsgIsZero()) && ((*z).AccountData.Status == 0) && ((*z).AccountData.MicroAlgos.MsgIsZero()) && ((*z).AccountData.RewardsBase == 0) && ((*z).AccountData.RewardedMicroAlgos.MsgIsZero()) && ((*z).AccountData.VoteID.MsgIsZero()) && ((*z).AccountData.SelectionID.MsgIsZero()) && ((*z).AccountData.StateProofID.MsgIsZero()) && ((*z).AccountData.VoteFirstValid == 0) && ((*z).AccountData.VoteLastValid == 0) && ((*z).AccountData.VoteKeyDilution == 0) && (len((*z).AccountData.AssetParams) == 0) && (len((*z).AccountData.Assets) == 0) && ((*z).AccountData.AuthAddr.MsgIsZero()) && (len((*z).AccountData.AppLocalStates) == 0) && (len((*z).AccountData.AppParams) == 0) && (((*z).AccountData.TotalAppSchema.NumUint == 0) && ((*z).AccountData.TotalAppSchema.NumByteSlice == 0)) && ((*z).AccountData.TotalExtraAppPages == 0) && ((*z).AccountData.TotalBoxes == 0) && ((*z).AccountData.TotalBoxBytes == 0) + return ((*z).Addr.MsgIsZero()) && ((*z).AccountData.Status == 0) && ((*z).AccountData.MicroAlgos.MsgIsZero()) && ((*z).AccountData.RewardsBase == 0) && ((*z).AccountData.RewardedMicroAlgos.MsgIsZero()) && ((*z).AccountData.VoteID.MsgIsZero()) && ((*z).AccountData.SelectionID.MsgIsZero()) && ((*z).AccountData.StateProofID.MsgIsZero()) && ((*z).AccountData.VoteFirstValid == 0) && ((*z).AccountData.VoteLastValid == 0) && ((*z).AccountData.VoteKeyDilution == 0) && ((*z).AccountData.LastProposed == 0) && ((*z).AccountData.LastHeartbeat == 0) && (len((*z).AccountData.AssetParams) == 0) && (len((*z).AccountData.Assets) == 0) && ((*z).AccountData.AuthAddr.MsgIsZero()) && ((*z).AccountData.IncentiveEligible == false) && (len((*z).AccountData.AppLocalStates) == 0) && (len((*z).AccountData.AppParams) == 0) && (((*z).AccountData.TotalAppSchema.NumUint == 0) && ((*z).AccountData.TotalAppSchema.NumByteSlice == 0)) && ((*z).AccountData.TotalExtraAppPages == 0) && ((*z).AccountData.TotalBoxes == 0) && ((*z).AccountData.TotalBoxBytes == 0) } // MaxSize returns a maximum valid message size for this message type func BalanceRecordMaxSize() (s int) { - s = 3 + 5 + AddressMaxSize() + 4 + msgp.ByteSize + 5 + MicroAlgosMaxSize() + 6 + msgp.Uint64Size + 4 + MicroAlgosMaxSize() + 5 + crypto.OneTimeSignatureVerifierMaxSize() + 4 + crypto.VRFVerifierMaxSize() + 6 + merklesignature.CommitmentMaxSize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 5 + s = 3 + 5 + AddressMaxSize() + 4 + msgp.ByteSize + 5 + MicroAlgosMaxSize() + 6 + msgp.Uint64Size + 4 + MicroAlgosMaxSize() + 5 + crypto.OneTimeSignatureVerifierMaxSize() + 4 + crypto.VRFVerifierMaxSize() + 6 + merklesignature.CommitmentMaxSize() + 8 + msgp.Uint64Size + 8 + msgp.Uint64Size + 7 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 s += msgp.MapHeaderSize // Adding size of map keys for z.AccountData.AssetParams s += encodedMaxAssetsPerAccount * (AssetIndexMaxSize()) @@ -4403,7 +4573,7 @@ func BalanceRecordMaxSize() (s int) { // Adding size of map values for z.AccountData.Assets s += encodedMaxAssetsPerAccount * (1) s += 2 + msgp.Uint64Size + 2 + msgp.BoolSize - s += 6 + AddressMaxSize() + 5 + s += 6 + AddressMaxSize() + 3 + msgp.BoolSize + 5 s += msgp.MapHeaderSize // Adding size of map keys for z.AccountData.AppLocalStates s += EncodedMaxAppLocalStates * (AppIndexMaxSize()) diff --git a/data/basics/overflow.go b/data/basics/overflow.go index 9f90577ec7..4c8ded599c 100644 --- a/data/basics/overflow.go +++ b/data/basics/overflow.go @@ -142,6 +142,14 @@ func (t *OverflowTracker) ScalarMulA(a MicroAlgos, b uint64) MicroAlgos { return MicroAlgos{Raw: t.Mul(a.Raw, b)} } +// MinA returns the smaller of 2 MicroAlgos values +func MinA(a, b MicroAlgos) MicroAlgos { + if a.Raw < b.Raw { + return a + } + return b +} + // Muldiv computes a*b/c. The overflow flag indicates that // the result was 2^64 or greater. func Muldiv(a uint64, b uint64, c uint64) (res uint64, overflow bool) { diff --git a/data/basics/units.go b/data/basics/units.go index af8743ee40..8e6ef0946c 100644 --- a/data/basics/units.go +++ b/data/basics/units.go @@ -17,6 +17,8 @@ package basics import ( + "math" + "github.com/algorand/go-codec/codec" "github.com/algorand/msgp/msgp" @@ -122,6 +124,17 @@ func MicroAlgosMaxSize() (s int) { return msgp.Uint64Size } +// Algos is a convenience function so that whole Algos can be written easily. It +// panics on overflow because it should only be used for constants - things that +// are best human-readable in source code - not used on arbitrary values from, +// say, transactions. +func Algos(algos uint64) MicroAlgos { + if algos > math.MaxUint64/1_000_000 { + panic(algos) + } + return MicroAlgos{Raw: algos * 1_000_000} +} + // Round represents a protocol round index type Round uint64 diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 398147bb6d..d8f86aea54 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -111,12 +111,18 @@ type VotingData struct { type OnlineAccountData struct { MicroAlgosWithRewards MicroAlgos VotingData + IncentiveEligible bool } // AccountData contains the data associated with a given address. // -// This includes the account balance, cryptographic public keys, -// consensus delegation status, asset data, and application data. +// This includes the account balance, cryptographic public keys, consensus +// status, asset params (for assets made by this account), asset holdings (for +// assets the account is opted into), and application data (globals if account +// created, locals if opted-in). This can be thought of as the fully "hydrated" +// structure and could take an arbitrary number of db queries to fill. As such, +// it is mostly used only for shuttling complete accounts into the ledger +// (genesis, catchpoints, REST API). And a lot of legacy tests. type AccountData struct { _struct struct{} `codec:",omitempty,omitemptyarray"` @@ -172,6 +178,13 @@ type AccountData struct { VoteLastValid Round `codec:"voteLst"` VoteKeyDilution uint64 `codec:"voteKD"` + // LastProposed is the last round that the account is known to have + // proposed. It is updated at the start of the _next_ round. + LastProposed Round `codec:"lpr"` + // LastHeartbeat is the last round an account has indicated it is ready to + // vote by sending a heartbeat transaction, signed by its partkey. + LastHeartbeat Round `codec:"lhb"` + // If this account created an asset, AssetParams stores // the parameters defining that asset. The params are indexed // by the Index of the AssetID; the Creator is this account's address. @@ -209,6 +222,11 @@ type AccountData struct { // This allows key rotation, changing the members in a multisig, etc. AuthAddr Address `codec:"spend"` + // IncentiveEligible indicates whether the account came online with the + // extra fee required to be eligible for block incentives. At proposal time, + // balance limits must also be met to receive incentives. + IncentiveEligible bool `codec:"ie"` + // AppLocalStates stores the local states associated with any applications // that this account has opted in to. AppLocalStates map[AppIndex]AppLocalState `codec:"appl,allocbound=EncodedMaxAppLocalStates"` @@ -465,7 +483,7 @@ func (u AccountData) WithUpdatedRewards(proto config.ConsensusParams, rewardsLev // MinBalance computes the minimum balance requirements for an account based on // some consensus parameters. MinBalance should correspond roughly to how much // storage the account is allowed to store on disk. -func (u AccountData) MinBalance(proto *config.ConsensusParams) (res MicroAlgos) { +func (u AccountData) MinBalance(proto *config.ConsensusParams) MicroAlgos { return MinBalance( proto, uint64(len(u.Assets)), @@ -486,7 +504,7 @@ func MinBalance( totalAppParams uint64, totalAppLocalStates uint64, totalExtraAppPages uint64, totalBoxes uint64, totalBoxBytes uint64, -) (res MicroAlgos) { +) MicroAlgos { var min uint64 // First, base MinBalance @@ -521,8 +539,7 @@ func MinBalance( boxByteCost := MulSaturate(proto.BoxByteMinBalance, totalBoxBytes) min = AddSaturate(min, boxByteCost) - res.Raw = min - return res + return MicroAlgos{min} } // OnlineAccountData returns subset of AccountData as OnlineAccountData data structure. @@ -543,6 +560,7 @@ func (u AccountData) OnlineAccountData() OnlineAccountData { VoteLastValid: u.VoteLastValid, VoteKeyDilution: u.VoteKeyDilution, }, + IncentiveEligible: u.IncentiveEligible, } } diff --git a/data/basics/userBalance_test.go b/data/basics/userBalance_test.go index da094329fd..c49afb9abf 100644 --- a/data/basics/userBalance_test.go +++ b/data/basics/userBalance_test.go @@ -128,6 +128,7 @@ func getSampleAccountData() AccountData { AppLocalStates: make(map[AppIndex]AppLocalState), AppParams: make(map[AppIndex]AppParams), AuthAddr: Address(crypto.Hash([]byte{1, 2, 3, 4})), + IncentiveEligible: true, } } @@ -190,4 +191,5 @@ func TestOnlineAccountData(t *testing.T) { require.Equal(t, ad.MicroAlgos, oad.MicroAlgosWithRewards) require.Equal(t, ad.VoteID, oad.VoteID) require.Equal(t, ad.SelectionID, oad.SelectionID) + require.Equal(t, ad.IncentiveEligible, oad.IncentiveEligible) } diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index d67621b343..7f2632f3f0 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -58,6 +58,26 @@ type ( // Genesis hash to which this block belongs. GenesisHash crypto.Digest `codec:"gh"` + // Proposer is the proposer of this block. Like the Seed, agreement adds + // this after the block is assembled by the transaction pool, so that the same block can be prepared + // for multiple participating accounts in the same node. Therefore, it can not be used + // to influence block evaluation. Populated if proto.Payouts.Enabled + Proposer basics.Address `codec:"prp"` + + // FeesCollected is the sum of all fees paid by transactions in this + // block. Populated if proto.Payouts.Enabled + FeesCollected basics.MicroAlgos `codec:"fc"` + + // Bonus is the bonus incentive to be paid for proposing this block. It + // begins as a consensus parameter value, and decays periodically. + Bonus basics.MicroAlgos `codec:"bi"` + + // ProposerPayout is the amount that should be moved from the FeeSink to + // the Proposer at the start of the next block. It is basically the + // bonus + the payouts percent of FeesCollected, but may be zero'd by + // proposer ineligibility. + ProposerPayout basics.MicroAlgos `codec:"pp"` + // Rewards. // // When a block is applied, some amount of rewards are accrued to @@ -145,6 +165,10 @@ type ( // that needs to be converted to offline since their // participation key expired. ExpiredParticipationAccounts []basics.Address `codec:"partupdrmv,allocbound=config.MaxProposedExpiredOnlineAccounts"` + + // AbsentParticipationAccounts contains a list of online accounts that + // needs to be converted to offline since they are not proposing. + AbsentParticipationAccounts []basics.Address `codec:"partupdabs,allocbound=config.MaxMarkAbsent"` } // RewardsState represents the global parameters controlling the rate @@ -274,18 +298,39 @@ func (block Block) GenesisHash() crypto.Digest { return block.BlockHeader.GenesisHash } -// WithSeed returns a copy of the Block with the seed set to s. -func (block Block) WithSeed(s committee.Seed) Block { - c := block - c.BlockHeader.Seed = s - return c -} - // Seed returns the Block's random seed. -func (block *Block) Seed() committee.Seed { +func (block Block) Seed() committee.Seed { return block.BlockHeader.Seed } +// Proposer returns the Block's proposer. +func (block Block) Proposer() basics.Address { + return block.BlockHeader.Proposer +} + +// ProposerPayout returns the Block's proposer payout. +func (block Block) ProposerPayout() basics.MicroAlgos { + return block.BlockHeader.ProposerPayout +} + +// WithProposer returns a copy of the Block with a modified seed and associated proposer +func (block Block) WithProposer(s committee.Seed, proposer basics.Address, eligible bool) Block { + newblock := block + newblock.BlockHeader.Seed = s + // agreement is telling us who the proposer is and if they're eligible, but + // agreement does not consider the current config params, so here we decide + // what really goes into the BlockHeader. + proto := config.Consensus[block.CurrentProtocol] + if proto.Payouts.Enabled { + newblock.BlockHeader.Proposer = proposer + } + if !proto.Payouts.Enabled || !eligible { + newblock.BlockHeader.ProposerPayout = basics.MicroAlgos{} + } + + return newblock +} + // NextRewardsState computes the RewardsState of the subsequent round // given the subsequent consensus parameters, along with the incentive pool // balance and the total reward units in the system as of the current round. @@ -467,6 +512,33 @@ func ProcessUpgradeParams(prev BlockHeader) (uv UpgradeVote, us UpgradeState, er return upgradeVote, upgradeState, err } +// NextBonus determines the bonus that should be paid out for proposing the next block. +func NextBonus(prev BlockHeader, params *config.ConsensusParams) basics.MicroAlgos { + current := uint64(prev.Round + 1) + prevParams := config.Consensus[prev.CurrentProtocol] // presence ensured by ProcessUpgradeParams + return computeBonus(current, prev.Bonus, params.Bonus, prevParams.Bonus) +} + +// computeBonus is the guts of NextBonus that can be unit tested more effectively. +func computeBonus(current uint64, prevBonus basics.MicroAlgos, curPlan config.BonusPlan, prevPlan config.BonusPlan) basics.MicroAlgos { + // Set the amount if it's non-zero... + if curPlan.BaseAmount != 0 { + upgrading := curPlan != prevPlan || current == 1 + // The time has come if the baseRound arrives, or at upgrade time if + // baseRound has already passed. + if current == curPlan.BaseRound || (upgrading && current > curPlan.BaseRound) { + return basics.MicroAlgos{Raw: curPlan.BaseAmount} + } + } + + if curPlan.DecayInterval != 0 && current%curPlan.DecayInterval == 0 { + // decay + keep, _ := basics.NewPercent(99).DivvyAlgos(prevBonus) + return keep + } + return prevBonus +} + // MakeBlock constructs a new valid block with an empty payset and an unset Seed. func MakeBlock(prev BlockHeader) Block { upgradeVote, upgradeState, err := ProcessUpgradeParams(prev) @@ -488,16 +560,19 @@ func MakeBlock(prev BlockHeader) Block { } } + bonus := NextBonus(prev, ¶ms) + // the merkle root of TXs will update when fillpayset is called blk := Block{ BlockHeader: BlockHeader{ Round: prev.Round + 1, Branch: prev.Hash(), - UpgradeVote: upgradeVote, - UpgradeState: upgradeState, TimeStamp: timestamp, GenesisID: prev.GenesisID, GenesisHash: prev.GenesisHash, + UpgradeVote: upgradeVote, + UpgradeState: upgradeState, + Bonus: bonus, }, } blk.TxnCommitments, err = blk.PaysetCommit() @@ -613,6 +688,12 @@ func (bh BlockHeader) PreCheck(prev BlockHeader) error { } } + // check bonus + expectedBonus := NextBonus(prev, ¶ms) + if bh.Bonus != expectedBonus { + return fmt.Errorf("bad bonus: %d != %d ", bh.Bonus, expectedBonus) + } + // Check genesis ID value against previous block, if set if bh.GenesisID == "" { return fmt.Errorf("genesis ID missing") diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go index 73cce6d7d4..3c305b3c3b 100644 --- a/data/bookkeeping/block_test.go +++ b/data/bookkeeping/block_test.go @@ -19,6 +19,7 @@ package bookkeeping import ( "bytes" "encoding/hex" + "fmt" "math" "testing" "time" @@ -54,6 +55,8 @@ func init() { params2 := config.Consensus[protocol.ConsensusCurrentVersion] params2.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + params2.Bonus.BaseAmount = 5_000_000 + params2.Bonus.DecayInterval = 1_000_000 config.Consensus[proto2] = params2 paramsDelay := config.Consensus[protocol.ConsensusCurrentVersion] @@ -67,6 +70,7 @@ func init() { func TestUpgradeVote(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() s := UpgradeState{ CurrentProtocol: proto1, @@ -130,6 +134,7 @@ func TestUpgradeVote(t *testing.T) { func TestUpgradeVariableDelay(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() s := UpgradeState{ CurrentProtocol: protoDelay, @@ -156,6 +161,7 @@ func TestUpgradeVariableDelay(t *testing.T) { func TestMakeBlockUpgrades(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var b Block b.BlockHeader.GenesisID = t.Name() @@ -208,6 +214,7 @@ func TestMakeBlockUpgrades(t *testing.T) { func TestBlockUnsupported(t *testing.T) { //nolint:paralleltest // Not parallel because it modifies config.Consensus partitiontest.PartitionTest(t) + // t.Parallel() not parallel because it modifies config.Consensus var b Block b.CurrentProtocol = protoUnsupported @@ -218,11 +225,12 @@ func TestBlockUnsupported(t *testing.T) { //nolint:paralleltest // Not parallel delete(config.Consensus, protoUnsupported) err := b1.PreCheck(b.BlockHeader) - require.Error(t, err) + require.ErrorContains(t, err, "protocol TestUnsupported not supported") } func TestTime(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var prev Block prev.BlockHeader.GenesisID = t.Name() @@ -243,15 +251,49 @@ func TestTime(t *testing.T) { require.NoError(t, b.PreCheck(prev.BlockHeader)) b.TimeStamp = prev.TimeStamp - 1 - require.Error(t, b.PreCheck(prev.BlockHeader)) + require.ErrorContains(t, b.PreCheck(prev.BlockHeader), "bad timestamp") b.TimeStamp = prev.TimeStamp + proto.MaxTimestampIncrement require.NoError(t, b.PreCheck(prev.BlockHeader)) b.TimeStamp = prev.TimeStamp + proto.MaxTimestampIncrement + 1 - require.Error(t, b.PreCheck(prev.BlockHeader)) + require.ErrorContains(t, b.PreCheck(prev.BlockHeader), "bad timestamp") +} + +func TestBonus(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var prev Block + prev.CurrentProtocol = proto1 + prev.BlockHeader.GenesisID = t.Name() + crypto.RandBytes(prev.BlockHeader.GenesisHash[:]) + + b := MakeBlock(prev.BlockHeader) + require.NoError(t, b.PreCheck(prev.BlockHeader)) + + // proto1 has no bonuses + b.Bonus.Raw++ + require.ErrorContains(t, b.PreCheck(prev.BlockHeader), "bad bonus: {1} != {0}") + + prev.CurrentProtocol = proto2 + prev.Bonus = basics.Algos(5) + b = MakeBlock(prev.BlockHeader) + require.NoError(t, b.PreCheck(prev.BlockHeader)) + + b.Bonus.Raw++ + require.ErrorContains(t, b.PreCheck(prev.BlockHeader), "bad bonus: {5000001} != {5000000}") + + prev.BlockHeader.Round = 10_000_000 - 1 + b = MakeBlock(prev.BlockHeader) + require.NoError(t, b.PreCheck(prev.BlockHeader)) + + // since current block is 0 mod decayInterval, bonus goes down to 4,950,000 + b.Bonus.Raw++ + require.ErrorContains(t, b.PreCheck(prev.BlockHeader), "bad bonus: {4950001} != {4950000}") } func TestRewardsLevel(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var buf bytes.Buffer log := logging.NewLogger() @@ -272,6 +314,7 @@ func TestRewardsLevel(t *testing.T) { func TestRewardsLevelWithResidue(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var buf bytes.Buffer log := logging.NewLogger() @@ -294,6 +337,7 @@ func TestRewardsLevelWithResidue(t *testing.T) { func TestRewardsLevelNoUnits(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var buf bytes.Buffer log := logging.NewLogger() @@ -315,6 +359,7 @@ func TestRewardsLevelNoUnits(t *testing.T) { func TestTinyLevel(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var buf bytes.Buffer log := logging.NewLogger() @@ -335,6 +380,7 @@ func TestTinyLevel(t *testing.T) { func TestRewardsRate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var buf bytes.Buffer log := logging.NewLogger() @@ -360,6 +406,7 @@ func TestRewardsRate(t *testing.T) { func TestRewardsRateRefresh(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var buf bytes.Buffer log := logging.NewLogger() @@ -385,6 +432,7 @@ func TestRewardsRateRefresh(t *testing.T) { func TestEncodeDecodeSignedTxn(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var b Block b.BlockHeader.GenesisID = "foo" @@ -405,6 +453,7 @@ func TestEncodeDecodeSignedTxn(t *testing.T) { func TestEncodeMalformedSignedTxn(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var b Block b.BlockHeader.GenesisID = "foo" @@ -430,6 +479,7 @@ func TestEncodeMalformedSignedTxn(t *testing.T) { func TestDecodeMalformedSignedTxn(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var b Block b.BlockHeader.GenesisID = "foo" @@ -451,6 +501,7 @@ func TestDecodeMalformedSignedTxn(t *testing.T) { // running the rounds in the same way eval() is executing them over RewardsRateRefreshInterval rounds. func TestInitialRewardsRateCalculation(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] consensusParams.RewardsCalculationFix = false @@ -553,6 +604,7 @@ func performRewardsRateCalculation( func TestNextRewardsRateWithFix(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() proto, ok := config.Consensus[protocol.ConsensusCurrentVersion] require.True(t, ok) @@ -598,6 +650,7 @@ func TestNextRewardsRateWithFix(t *testing.T) { func TestNextRewardsRateFailsWithoutFix(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() proto, ok := config.Consensus[protocol.ConsensusCurrentVersion] require.True(t, ok) @@ -617,6 +670,7 @@ func TestNextRewardsRateFailsWithoutFix(t *testing.T) { func TestNextRewardsRateWithFixUsesNewRate(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() proto, ok := config.Consensus[protocol.ConsensusCurrentVersion] require.True(t, ok) @@ -651,6 +705,7 @@ func TestNextRewardsRateWithFixUsesNewRate(t *testing.T) { func TestNextRewardsRateWithFixPoolBalanceInsufficient(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() proto, ok := config.Consensus[protocol.ConsensusCurrentVersion] require.True(t, ok) @@ -685,6 +740,7 @@ func TestNextRewardsRateWithFixPoolBalanceInsufficient(t *testing.T) { func TestNextRewardsRateWithFixMaxSpentOverOverflow(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() proto, ok := config.Consensus[protocol.ConsensusCurrentVersion] require.True(t, ok) @@ -721,6 +777,7 @@ func TestNextRewardsRateWithFixMaxSpentOverOverflow(t *testing.T) { func TestNextRewardsRateWithFixRewardsWithResidueOverflow(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() proto, ok := config.Consensus[protocol.ConsensusCurrentVersion] require.True(t, ok) @@ -747,6 +804,7 @@ func TestNextRewardsRateWithFixRewardsWithResidueOverflow(t *testing.T) { func TestNextRewardsRateWithFixNextRewardLevelOverflow(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() proto, ok := config.Consensus[protocol.ConsensusCurrentVersion] require.True(t, ok) @@ -773,6 +831,7 @@ func TestNextRewardsRateWithFixNextRewardLevelOverflow(t *testing.T) { func TestBlock_ContentsMatchHeader(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) // Create a block without SHA256 TxnCommitments @@ -860,6 +919,7 @@ func TestBlock_ContentsMatchHeader(t *testing.T) { func TestBlockHeader_Serialization(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() a := require.New(t) // This serialized block header was generated from V32 e2e test, using the old BlockHeader struct which contains only TxnCommitments SHA512_256 value @@ -874,3 +934,128 @@ func TestBlockHeader_Serialization(t *testing.T) { a.Equal(crypto.Digest{}, blkHdr.TxnCommitments.Sha256Commitment) a.NotEqual(crypto.Digest{}, blkHdr.TxnCommitments.NativeSha512_256Commitment) } + +func TestBonusUpgrades(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := require.New(t) + + ma0 := basics.MicroAlgos{Raw: 0} + ma99 := basics.MicroAlgos{Raw: 99} + ma100 := basics.MicroAlgos{Raw: 100} + ma198 := basics.MicroAlgos{Raw: 198} + ma200 := basics.MicroAlgos{Raw: 200} + + old := config.BonusPlan{} + plan := config.BonusPlan{} + + // Nothing happens with empty plans + a.Equal(ma0, computeBonus(1, ma0, plan, old)) + a.Equal(ma100, computeBonus(1, ma100, plan, old)) + + // When plan doesn't change, just expect decay on the intervals + plan.DecayInterval = 100 + a.Equal(ma100, computeBonus(1, ma100, plan, plan)) + a.Equal(ma100, computeBonus(99, ma100, plan, plan)) + a.Equal(ma99, computeBonus(100, ma100, plan, plan)) + a.Equal(ma100, computeBonus(101, ma100, plan, plan)) + a.Equal(ma99, computeBonus(10000, ma100, plan, plan)) + + // When plan changes, the new decay is in effect + d90 := config.BonusPlan{DecayInterval: 90} + a.Equal(ma100, computeBonus(100, ma100, d90, plan)) // no decay + a.Equal(ma99, computeBonus(180, ma100, d90, plan)) // decay + + // When plan changes and amount is present, it is installed + d90.BaseAmount = 200 + a.Equal(ma200, computeBonus(100, ma100, d90, plan)) // no decay (wrong round and upgrade anyway) + a.Equal(ma200, computeBonus(180, ma100, d90, plan)) // no decay (upgrade) + a.Equal(ma198, computeBonus(180, ma200, d90, d90)) // decay + a.Equal(ma99, computeBonus(180, ma100, d90, d90)) // decay (no install) + + // If there's a baseRound, the amount is installed accordingly + d90.BaseRound = 150 + a.Equal(ma99, computeBonus(90, ma100, d90, plan)) // decay because baseRound delays install + a.Equal(ma100, computeBonus(149, ma100, d90, plan)) // no decay (interval) but also not installed yet + a.Equal(ma200, computeBonus(150, ma100, d90, plan)) // no decay (upgrade and immediate change) + a.Equal(ma200, computeBonus(151, ma100, d90, plan)) // no decay (upgrade and immediate change) + + // same tests, but not the upgrade round. only the "immediate installs" changes + a.Equal(ma99, computeBonus(90, ma100, d90, d90)) // decay + a.Equal(ma100, computeBonus(149, ma100, d90, d90)) // no decay (interval) but also not installed yet + a.Equal(ma200, computeBonus(150, ma100, d90, d90)) // not upgrade, but baseRound means install time + a.Equal(ma100, computeBonus(151, ma100, d90, d90)) // no decay (interval) +} + +// TestFirstYearsBonus shows what the bonuses look like +func TestFirstYearsBonus(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := require.New(t) + + yearSeconds := 365 * 24 * 60 * 60 + yearRounds := int(float64(yearSeconds) / 2.9) + + plan := config.Consensus[protocol.ConsensusFuture].Bonus + sum := uint64(0) + bonus := plan.BaseAmount + interval := int(plan.DecayInterval) + r := 0 + for i := 0; i < yearRounds; i++ { + r++ + sum += bonus + if r%interval == 0 { + bonus, _ = basics.Muldiv(bonus, 99, 100) + } + } + suma := sum / 1_000_000 // micro to Algos + + fmt.Printf("paid %d algos\n", suma) + fmt.Printf("bonus start: %d end: %d\n", plan.BaseAmount, bonus) + + // pays about 88M algos + a.InDelta(88_500_000, suma, 100_000) + + // decline about 35% + a.InDelta(0.65, float64(bonus)/float64(plan.BaseAmount), 0.01) + + // year 2 + for i := 0; i < yearRounds; i++ { + r++ + sum += bonus + if r%interval == 0 { + bonus, _ = basics.Muldiv(bonus, 99, 100) + } + } + + sum2 := sum / 1_000_000 // micro to Algos + + fmt.Printf("paid %d algos after 2 years\n", sum2) + fmt.Printf("bonus end: %d\n", bonus) + + // pays about 146M algos (total for 2 years) + a.InDelta(145_700_000, sum2, 100_000) + + // decline about 58% + a.InDelta(0.42, float64(bonus)/float64(plan.BaseAmount), 0.01) + + // year 3 + for i := 0; i < yearRounds; i++ { + r++ + sum += bonus + if r%interval == 0 { + bonus, _ = basics.Muldiv(bonus, 99, 100) + } + } + + sum3 := sum / 1_000_000 // micro to Algos + + fmt.Printf("paid %d algos after 3 years\n", sum3) + fmt.Printf("bonus end: %d\n", bonus) + + // pays about 182M algos (total for 3 years) + a.InDelta(182_600_000, sum3, 100_000) + + // declined to about 27% (but foundation funding probably gone anyway) + a.InDelta(0.27, float64(bonus)/float64(plan.BaseAmount), 0.01) +} diff --git a/data/bookkeeping/msgp_gen.go b/data/bookkeeping/msgp_gen.go index cb3a63ad2e..fef26fd1b9 100644 --- a/data/bookkeeping/msgp_gen.go +++ b/data/bookkeeping/msgp_gen.go @@ -143,161 +143,203 @@ import ( func (z *Block) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0004Len := uint32(26) - var zb0004Mask uint32 /* 31 bits */ + zb0005Len := uint32(31) + var zb0005Mask uint64 /* 36 bits */ + if (*z).BlockHeader.Bonus.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x20 + } if (*z).BlockHeader.RewardsState.RewardsLevel == 0 { - zb0004Len-- - zb0004Mask |= 0x20 + zb0005Len-- + zb0005Mask |= 0x40 + } + if (*z).BlockHeader.FeesCollected.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x80 } if (*z).BlockHeader.RewardsState.FeeSink.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40 + zb0005Len-- + zb0005Mask |= 0x100 } if (*z).BlockHeader.RewardsState.RewardsResidue == 0 { - zb0004Len-- - zb0004Mask |= 0x80 + zb0005Len-- + zb0005Mask |= 0x200 } if (*z).BlockHeader.GenesisID == "" { - zb0004Len-- - zb0004Mask |= 0x100 + zb0005Len-- + zb0005Mask |= 0x400 } if (*z).BlockHeader.GenesisHash.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200 + zb0005Len-- + zb0005Mask |= 0x800 } if (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400 + zb0005Len-- + zb0005Mask |= 0x1000 } if (*z).BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800 + zb0005Len-- + zb0005Mask |= 0x2000 } if (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000 + zb0005Len-- + zb0005Mask |= 0x4000 } if (*z).BlockHeader.UpgradeState.NextProtocolApprovals == 0 { - zb0004Len-- - zb0004Mask |= 0x2000 + zb0005Len-- + zb0005Mask |= 0x8000 + } + if len((*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0 { + zb0005Len-- + zb0005Mask |= 0x10000 } if len((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { - zb0004Len-- - zb0004Mask |= 0x4000 + zb0005Len-- + zb0005Mask |= 0x20000 + } + if (*z).BlockHeader.ProposerPayout.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x40000 } if (*z).BlockHeader.Branch.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x8000 + zb0005Len-- + zb0005Mask |= 0x80000 } if (*z).BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x10000 + zb0005Len-- + zb0005Mask |= 0x100000 + } + if (*z).BlockHeader.Proposer.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x200000 } if (*z).BlockHeader.RewardsState.RewardsRate == 0 { - zb0004Len-- - zb0004Mask |= 0x20000 + zb0005Len-- + zb0005Mask |= 0x400000 } if (*z).BlockHeader.Round.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40000 + zb0005Len-- + zb0005Mask |= 0x800000 } if (*z).BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80000 + zb0005Len-- + zb0005Mask |= 0x1000000 } if (*z).BlockHeader.RewardsState.RewardsPool.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x100000 + zb0005Len-- + zb0005Mask |= 0x2000000 } if (*z).BlockHeader.Seed.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200000 + zb0005Len-- + zb0005Mask |= 0x4000000 } if len((*z).BlockHeader.StateProofTracking) == 0 { - zb0004Len-- - zb0004Mask |= 0x400000 + zb0005Len-- + zb0005Mask |= 0x8000000 } if (*z).BlockHeader.TxnCounter == 0 { - zb0004Len-- - zb0004Mask |= 0x800000 + zb0005Len-- + zb0005Mask |= 0x10000000 } if (*z).BlockHeader.TimeStamp == 0 { - zb0004Len-- - zb0004Mask |= 0x1000000 + zb0005Len-- + zb0005Mask |= 0x20000000 } if (*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000000 + zb0005Len-- + zb0005Mask |= 0x40000000 } if (*z).BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x4000000 + zb0005Len-- + zb0005Mask |= 0x80000000 } if (*z).Payset.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x8000000 + zb0005Len-- + zb0005Mask |= 0x100000000 } if (*z).BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x10000000 + zb0005Len-- + zb0005Mask |= 0x200000000 } if (*z).BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x20000000 + zb0005Len-- + zb0005Mask |= 0x400000000 } if (*z).BlockHeader.UpgradeVote.UpgradeApprove == false { - zb0004Len-- - zb0004Mask |= 0x40000000 + zb0005Len-- + zb0005Mask |= 0x800000000 } - // variable map header, size zb0004Len - o = msgp.AppendMapHeader(o, zb0004Len) - if zb0004Len != 0 { - if (zb0004Mask & 0x20) == 0 { // if not empty + // variable map header, size zb0005Len + o = msgp.AppendMapHeader(o, zb0005Len) + if zb0005Len != 0 { + if (zb0005Mask & 0x20) == 0 { // if not empty + // string "bi" + o = append(o, 0xa2, 0x62, 0x69) + o = (*z).BlockHeader.Bonus.MarshalMsg(o) + } + if (zb0005Mask & 0x40) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x40) == 0 { // if not empty + if (zb0005Mask & 0x80) == 0 { // if not empty + // string "fc" + o = append(o, 0xa2, 0x66, 0x63) + o = (*z).BlockHeader.FeesCollected.MarshalMsg(o) + } + if (zb0005Mask & 0x100) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x80) == 0 { // if not empty + if (zb0005Mask & 0x200) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0005Mask & 0x400) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).BlockHeader.GenesisID) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0005Mask & 0x800) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0005Mask & 0x1000) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0005Mask & 0x2000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0005Mask & 0x4000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0005Mask & 0x8000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0005Mask & 0x10000) == 0 { // if not empty + // string "partupdabs" + o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x61, 0x62, 0x73) + if (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts))) + } + for zb0004 := range (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + o = (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].MarshalMsg(o) + } + } + if (zb0005Mask & 0x20000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -309,42 +351,52 @@ func (z *Block) MarshalMsg(b []byte) (o []byte) { o = (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0005Mask & 0x40000) == 0 { // if not empty + // string "pp" + o = append(o, 0xa2, 0x70, 0x70) + o = (*z).BlockHeader.ProposerPayout.MarshalMsg(o) + } + if (zb0005Mask & 0x80000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0005Mask & 0x100000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0005Mask & 0x200000) == 0 { // if not empty + // string "prp" + o = append(o, 0xa3, 0x70, 0x72, 0x70) + o = (*z).BlockHeader.Proposer.MarshalMsg(o) + } + if (zb0005Mask & 0x400000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0005Mask & 0x800000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0005Mask & 0x1000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0005Mask & 0x2000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0005Mask & 0x4000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).BlockHeader.Seed.MarshalMsg(o) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0005Mask & 0x8000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).BlockHeader.StateProofTracking == nil { @@ -364,42 +416,42 @@ func (z *Block) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0005Mask & 0x10000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).BlockHeader.TxnCounter) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0005Mask & 0x20000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).BlockHeader.TimeStamp) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0005Mask & 0x40000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0005Mask & 0x80000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).BlockHeader.TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0005Mask & 0x100000000) == 0 { // if not empty // string "txns" o = append(o, 0xa4, 0x74, 0x78, 0x6e, 0x73) o = (*z).Payset.MarshalMsg(o) } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0005Mask & 0x200000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).BlockHeader.UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0005Mask & 0x400000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).BlockHeader.UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x40000000) == 0 { // if not empty + if (zb0005Mask & 0x800000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).BlockHeader.UpgradeVote.UpgradeApprove) @@ -422,73 +474,73 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b st.AllowableDepth-- var field []byte _ = field - var zb0004 int - var zb0005 bool - zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.Round.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Round") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.Branch.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Branch") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.Seed.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Seed") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NativeSha512_256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.TxnCommitments.Sha256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Sha256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).BlockHeader.TimeStamp, bts, err = msgp.ReadInt64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TimeStamp") return } } - if zb0004 > 0 { - zb0004-- - var zb0006 int - zb0006, err = msgp.ReadBytesBytesHeader(bts) + if zb0005 > 0 { + zb0005-- + var zb0007 int + zb0007, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisID") return } - if zb0006 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0006), uint64(config.MaxGenesisIDLen)) + if zb0007 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxGenesisIDLen)) return } (*z).BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -497,157 +549,189 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.GenesisHash.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisHash") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + bts, err = (*z).BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Proposer") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "FeesCollected") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Bonus") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ProposerPayout") + return + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FeeSink") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.RewardsState.RewardsPool.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsPool") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).BlockHeader.RewardsState.RewardsLevel, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsLevel") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).BlockHeader.RewardsState.RewardsRate, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRate") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).BlockHeader.RewardsState.RewardsResidue, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsResidue") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.RewardsState.RewardsRecalculationRound.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRecalculationRound") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.UpgradeState.CurrentProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CurrentProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.UpgradeState.NextProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).BlockHeader.UpgradeState.NextProtocolApprovals, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolApprovals") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolVoteBefore") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolSwitchOn") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.UpgradeVote.UpgradePropose.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradePropose") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).BlockHeader.UpgradeVote.UpgradeDelay.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeDelay") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).BlockHeader.UpgradeVote.UpgradeApprove, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeApprove") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).BlockHeader.TxnCounter, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TxnCounter") return } } - if zb0004 > 0 { - zb0004-- - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0007 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0007), uint64(protocol.NumStateProofTypes)) + if zb0008 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0008), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0008 { + if zb0009 { (*z).BlockHeader.StateProofTracking = nil } else if (*z).BlockHeader.StateProofTracking == nil { - (*z).BlockHeader.StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0007) + (*z).BlockHeader.StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0008) } - for zb0007 > 0 { + for zb0008 > 0 { var zb0001 protocol.StateProofType var zb0002 StateProofTrackingData - zb0007-- + zb0008-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") @@ -661,26 +745,26 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b (*z).BlockHeader.StateProofTracking[zb0001] = zb0002 } } - if zb0004 > 0 { - zb0004-- - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0009 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0010 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0010), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0010 { + if zb0011 { (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0009 { - (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0009] + } else if (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0010 { + (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0010] } else { - (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0009) + (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0010) } for zb0003 := range (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -690,16 +774,45 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b } } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0012 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0013 { + (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0012 { + (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0012] + } else { + (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0012) + } + for zb0004 := range (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts", zb0004) + return + } + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).Payset.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Payset") return } } - if zb0004 > 0 { - err = msgp.ErrTooManyArrayFields(zb0004) + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -710,11 +823,11 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b err = msgp.WrapError(err) return } - if zb0005 { + if zb0006 { (*z) = Block{} } - for zb0004 > 0 { - zb0004-- + for zb0005 > 0 { + zb0005-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -758,14 +871,14 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b return } case "gen": - var zb0011 int - zb0011, err = msgp.ReadBytesBytesHeader(bts) + var zb0014 int + zb0014, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "GenesisID") return } - if zb0011 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0011), uint64(config.MaxGenesisIDLen)) + if zb0014 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0014), uint64(config.MaxGenesisIDLen)) return } (*z).BlockHeader.GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -779,6 +892,30 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b err = msgp.WrapError(err, "GenesisHash") return } + case "prp": + bts, err = (*z).BlockHeader.Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Proposer") + return + } + case "fc": + bts, err = (*z).BlockHeader.FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "FeesCollected") + return + } + case "bi": + bts, err = (*z).BlockHeader.Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Bonus") + return + } + case "pp": + bts, err = (*z).BlockHeader.ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ProposerPayout") + return + } case "fees": bts, err = (*z).BlockHeader.RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { @@ -870,27 +1007,27 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b return } case "spt": - var zb0012 int - var zb0013 bool - zb0012, zb0013, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "StateProofTracking") return } - if zb0012 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0012), uint64(protocol.NumStateProofTypes)) + if zb0015 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0015), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "StateProofTracking") return } - if zb0013 { + if zb0016 { (*z).BlockHeader.StateProofTracking = nil } else if (*z).BlockHeader.StateProofTracking == nil { - (*z).BlockHeader.StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0012) + (*z).BlockHeader.StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0015) } - for zb0012 > 0 { + for zb0015 > 0 { var zb0001 protocol.StateProofType var zb0002 StateProofTrackingData - zb0012-- + zb0015-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "StateProofTracking") @@ -904,24 +1041,24 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b (*z).BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0017 int + var zb0018 bool + zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0014 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0014), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0017 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0017), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0015 { + if zb0018 { (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0014 { - (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0014] + } else if (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) >= zb0017 { + (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = ((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts)[:zb0017] } else { - (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0014) + (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0017) } for zb0003 := range (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -930,6 +1067,33 @@ func (z *Block) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []b return } } + case "partupdabs": + var zb0019 int + var zb0020 bool + zb0019, zb0020, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0019 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0019), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0020 { + (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) >= zb0019 { + (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = ((*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts)[:zb0019] + } else { + (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0019) + } + for zb0004 := range (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts", zb0004) + return + } + } case "txns": bts, err = (*z).Payset.UnmarshalMsgWithState(bts, st) if err != nil { @@ -959,7 +1123,7 @@ func (_ *Block) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *Block) Msgsize() (s int) { - s = 3 + 4 + (*z).BlockHeader.Round.Msgsize() + 5 + (*z).BlockHeader.Branch.Msgsize() + 5 + (*z).BlockHeader.Seed.Msgsize() + 4 + (*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).BlockHeader.GenesisID) + 3 + (*z).BlockHeader.GenesisHash.Msgsize() + 5 + (*z).BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + s = 3 + 4 + (*z).BlockHeader.Round.Msgsize() + 5 + (*z).BlockHeader.Branch.Msgsize() + 5 + (*z).BlockHeader.Seed.Msgsize() + 4 + (*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).BlockHeader.GenesisID) + 3 + (*z).BlockHeader.GenesisHash.Msgsize() + 4 + (*z).BlockHeader.Proposer.Msgsize() + 3 + (*z).BlockHeader.FeesCollected.Msgsize() + 3 + (*z).BlockHeader.Bonus.Msgsize() + 3 + (*z).BlockHeader.ProposerPayout.Msgsize() + 5 + (*z).BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize if (*z).BlockHeader.StateProofTracking != nil { for zb0001, zb0002 := range (*z).BlockHeader.StateProofTracking { _ = zb0001 @@ -971,18 +1135,22 @@ func (z *Block) Msgsize() (s int) { for zb0003 := range (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts { s += (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].Msgsize() } + s += 11 + msgp.ArrayHeaderSize + for zb0004 := range (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts { + s += (*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts[zb0004].Msgsize() + } s += 5 + (*z).Payset.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *Block) MsgIsZero() bool { - return ((*z).BlockHeader.Round.MsgIsZero()) && ((*z).BlockHeader.Branch.MsgIsZero()) && ((*z).BlockHeader.Seed.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).BlockHeader.TimeStamp == 0) && ((*z).BlockHeader.GenesisID == "") && ((*z).BlockHeader.GenesisHash.MsgIsZero()) && ((*z).BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).BlockHeader.RewardsState.RewardsRate == 0) && ((*z).BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).BlockHeader.TxnCounter == 0) && (len((*z).BlockHeader.StateProofTracking) == 0) && (len((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).Payset.MsgIsZero()) + return ((*z).BlockHeader.Round.MsgIsZero()) && ((*z).BlockHeader.Branch.MsgIsZero()) && ((*z).BlockHeader.Seed.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).BlockHeader.TimeStamp == 0) && ((*z).BlockHeader.GenesisID == "") && ((*z).BlockHeader.GenesisHash.MsgIsZero()) && ((*z).BlockHeader.Proposer.MsgIsZero()) && ((*z).BlockHeader.FeesCollected.MsgIsZero()) && ((*z).BlockHeader.Bonus.MsgIsZero()) && ((*z).BlockHeader.ProposerPayout.MsgIsZero()) && ((*z).BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).BlockHeader.RewardsState.RewardsRate == 0) && ((*z).BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).BlockHeader.TxnCounter == 0) && (len((*z).BlockHeader.StateProofTracking) == 0) && (len((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && (len((*z).BlockHeader.ParticipationUpdates.AbsentParticipationAccounts) == 0) && ((*z).Payset.MsgIsZero()) } // MaxSize returns a maximum valid message size for this message type func BlockMaxSize() (s int) { - s = 3 + 4 + basics.RoundMaxSize() + 5 + BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + s = 3 + 4 + basics.RoundMaxSize() + 5 + BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 4 + basics.AddressMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 s += msgp.MapHeaderSize // Adding size of map keys for z.BlockHeader.StateProofTracking s += protocol.NumStateProofTypes * (protocol.StateProofTypeMaxSize()) @@ -991,6 +1159,9 @@ func BlockMaxSize() (s int) { s += 11 // Calculating size of slice: z.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts s += msgp.ArrayHeaderSize + ((config.MaxProposedExpiredOnlineAccounts) * (basics.AddressMaxSize())) + s += 11 + // Calculating size of slice: z.BlockHeader.ParticipationUpdates.AbsentParticipationAccounts + s += msgp.ArrayHeaderSize + ((config.MaxMarkAbsent) * (basics.AddressMaxSize())) s += 5 // Using maxtotalbytes for: z.Payset s += config.MaxTxnBytesPerBlock @@ -1037,157 +1208,199 @@ func BlockHashMaxSize() int { func (z *BlockHeader) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0004Len := uint32(25) - var zb0004Mask uint32 /* 30 bits */ + zb0005Len := uint32(30) + var zb0005Mask uint64 /* 35 bits */ + if (*z).Bonus.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x20 + } if (*z).RewardsState.RewardsLevel == 0 { - zb0004Len-- - zb0004Mask |= 0x20 + zb0005Len-- + zb0005Mask |= 0x40 + } + if (*z).FeesCollected.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x80 } if (*z).RewardsState.FeeSink.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40 + zb0005Len-- + zb0005Mask |= 0x100 } if (*z).RewardsState.RewardsResidue == 0 { - zb0004Len-- - zb0004Mask |= 0x80 + zb0005Len-- + zb0005Mask |= 0x200 } if (*z).GenesisID == "" { - zb0004Len-- - zb0004Mask |= 0x100 + zb0005Len-- + zb0005Mask |= 0x400 } if (*z).GenesisHash.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200 + zb0005Len-- + zb0005Mask |= 0x800 } if (*z).UpgradeState.NextProtocolVoteBefore.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x400 + zb0005Len-- + zb0005Mask |= 0x1000 } if (*z).UpgradeState.NextProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x800 + zb0005Len-- + zb0005Mask |= 0x2000 } if (*z).UpgradeState.NextProtocolSwitchOn.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x1000 + zb0005Len-- + zb0005Mask |= 0x4000 } if (*z).UpgradeState.NextProtocolApprovals == 0 { - zb0004Len-- - zb0004Mask |= 0x2000 + zb0005Len-- + zb0005Mask |= 0x8000 + } + if len((*z).ParticipationUpdates.AbsentParticipationAccounts) == 0 { + zb0005Len-- + zb0005Mask |= 0x10000 } if len((*z).ParticipationUpdates.ExpiredParticipationAccounts) == 0 { - zb0004Len-- - zb0004Mask |= 0x4000 + zb0005Len-- + zb0005Mask |= 0x20000 + } + if (*z).ProposerPayout.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x40000 } if (*z).Branch.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x8000 + zb0005Len-- + zb0005Mask |= 0x80000 } if (*z).UpgradeState.CurrentProtocol.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x10000 + zb0005Len-- + zb0005Mask |= 0x100000 + } + if (*z).Proposer.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x200000 } if (*z).RewardsState.RewardsRate == 0 { - zb0004Len-- - zb0004Mask |= 0x20000 + zb0005Len-- + zb0005Mask |= 0x400000 } if (*z).Round.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x40000 + zb0005Len-- + zb0005Mask |= 0x800000 } if (*z).RewardsState.RewardsRecalculationRound.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x80000 + zb0005Len-- + zb0005Mask |= 0x1000000 } if (*z).RewardsState.RewardsPool.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x100000 + zb0005Len-- + zb0005Mask |= 0x2000000 } if (*z).Seed.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x200000 + zb0005Len-- + zb0005Mask |= 0x4000000 } if len((*z).StateProofTracking) == 0 { - zb0004Len-- - zb0004Mask |= 0x400000 + zb0005Len-- + zb0005Mask |= 0x8000000 } if (*z).TxnCounter == 0 { - zb0004Len-- - zb0004Mask |= 0x800000 + zb0005Len-- + zb0005Mask |= 0x10000000 } if (*z).TimeStamp == 0 { - zb0004Len-- - zb0004Mask |= 0x1000000 + zb0005Len-- + zb0005Mask |= 0x20000000 } if (*z).TxnCommitments.NativeSha512_256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x2000000 + zb0005Len-- + zb0005Mask |= 0x40000000 } if (*z).TxnCommitments.Sha256Commitment.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x4000000 + zb0005Len-- + zb0005Mask |= 0x80000000 } if (*z).UpgradeVote.UpgradeDelay.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x8000000 + zb0005Len-- + zb0005Mask |= 0x100000000 } if (*z).UpgradeVote.UpgradePropose.MsgIsZero() { - zb0004Len-- - zb0004Mask |= 0x10000000 + zb0005Len-- + zb0005Mask |= 0x200000000 } if (*z).UpgradeVote.UpgradeApprove == false { - zb0004Len-- - zb0004Mask |= 0x20000000 + zb0005Len-- + zb0005Mask |= 0x400000000 } - // variable map header, size zb0004Len - o = msgp.AppendMapHeader(o, zb0004Len) - if zb0004Len != 0 { - if (zb0004Mask & 0x20) == 0 { // if not empty + // variable map header, size zb0005Len + o = msgp.AppendMapHeader(o, zb0005Len) + if zb0005Len != 0 { + if (zb0005Mask & 0x20) == 0 { // if not empty + // string "bi" + o = append(o, 0xa2, 0x62, 0x69) + o = (*z).Bonus.MarshalMsg(o) + } + if (zb0005Mask & 0x40) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).RewardsState.RewardsLevel) } - if (zb0004Mask & 0x40) == 0 { // if not empty + if (zb0005Mask & 0x80) == 0 { // if not empty + // string "fc" + o = append(o, 0xa2, 0x66, 0x63) + o = (*z).FeesCollected.MarshalMsg(o) + } + if (zb0005Mask & 0x100) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x80) == 0 { // if not empty + if (zb0005Mask & 0x200) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).RewardsState.RewardsResidue) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0005Mask & 0x400) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).GenesisID) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0005Mask & 0x800) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0005Mask & 0x1000) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0005Mask & 0x2000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0005Mask & 0x4000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0005Mask & 0x8000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0005Mask & 0x10000) == 0 { // if not empty + // string "partupdabs" + o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x61, 0x62, 0x73) + if (*z).ParticipationUpdates.AbsentParticipationAccounts == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).ParticipationUpdates.AbsentParticipationAccounts))) + } + for zb0004 := range (*z).ParticipationUpdates.AbsentParticipationAccounts { + o = (*z).ParticipationUpdates.AbsentParticipationAccounts[zb0004].MarshalMsg(o) + } + } + if (zb0005Mask & 0x20000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -1199,42 +1412,52 @@ func (z *BlockHeader) MarshalMsg(b []byte) (o []byte) { o = (*z).ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0005Mask & 0x40000) == 0 { // if not empty + // string "pp" + o = append(o, 0xa2, 0x70, 0x70) + o = (*z).ProposerPayout.MarshalMsg(o) + } + if (zb0005Mask & 0x80000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).Branch.MarshalMsg(o) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0005Mask & 0x100000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0005Mask & 0x200000) == 0 { // if not empty + // string "prp" + o = append(o, 0xa3, 0x70, 0x72, 0x70) + o = (*z).Proposer.MarshalMsg(o) + } + if (zb0005Mask & 0x400000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).RewardsState.RewardsRate) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0005Mask & 0x800000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).Round.MarshalMsg(o) } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0005Mask & 0x1000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0005Mask & 0x2000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0005Mask & 0x4000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).Seed.MarshalMsg(o) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0005Mask & 0x8000000) == 0 { // if not empty // string "spt" o = append(o, 0xa3, 0x73, 0x70, 0x74) if (*z).StateProofTracking == nil { @@ -1254,37 +1477,37 @@ func (z *BlockHeader) MarshalMsg(b []byte) (o []byte) { o = zb0002.MarshalMsg(o) } } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0005Mask & 0x10000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) o = msgp.AppendUint64(o, (*z).TxnCounter) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0005Mask & 0x20000000) == 0 { // if not empty // string "ts" o = append(o, 0xa2, 0x74, 0x73) o = msgp.AppendInt64(o, (*z).TimeStamp) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0005Mask & 0x40000000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).TxnCommitments.NativeSha512_256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0005Mask & 0x80000000) == 0 { // if not empty // string "txn256" o = append(o, 0xa6, 0x74, 0x78, 0x6e, 0x32, 0x35, 0x36) o = (*z).TxnCommitments.Sha256Commitment.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0005Mask & 0x100000000) == 0 { // if not empty // string "upgradedelay" o = append(o, 0xac, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79) o = (*z).UpgradeVote.UpgradeDelay.MarshalMsg(o) } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0005Mask & 0x200000000) == 0 { // if not empty // string "upgradeprop" o = append(o, 0xab, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x70) o = (*z).UpgradeVote.UpgradePropose.MarshalMsg(o) } - if (zb0004Mask & 0x20000000) == 0 { // if not empty + if (zb0005Mask & 0x400000000) == 0 { // if not empty // string "upgradeyes" o = append(o, 0xaa, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x65, 0x73) o = msgp.AppendBool(o, (*z).UpgradeVote.UpgradeApprove) @@ -1307,73 +1530,73 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) st.AllowableDepth-- var field []byte _ = field - var zb0004 int - var zb0005 bool - zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Round.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Round") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Branch.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Branch") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).Seed.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Seed") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).TxnCommitments.NativeSha512_256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NativeSha512_256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).TxnCommitments.Sha256Commitment.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "Sha256Commitment") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).TimeStamp, bts, err = msgp.ReadInt64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TimeStamp") return } } - if zb0004 > 0 { - zb0004-- - var zb0006 int - zb0006, err = msgp.ReadBytesBytesHeader(bts) + if zb0005 > 0 { + zb0005-- + var zb0007 int + zb0007, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisID") return } - if zb0006 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0006), uint64(config.MaxGenesisIDLen)) + if zb0007 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxGenesisIDLen)) return } (*z).GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -1382,157 +1605,189 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).GenesisHash.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "GenesisHash") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- + bts, err = (*z).Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Proposer") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "FeesCollected") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Bonus") + return + } + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ProposerPayout") + return + } + } + if zb0005 > 0 { + zb0005-- bts, err = (*z).RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "FeeSink") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).RewardsState.RewardsPool.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsPool") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).RewardsState.RewardsLevel, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsLevel") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).RewardsState.RewardsRate, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRate") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).RewardsState.RewardsResidue, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsResidue") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).RewardsState.RewardsRecalculationRound.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "RewardsRecalculationRound") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).UpgradeState.CurrentProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "CurrentProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).UpgradeState.NextProtocol.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocol") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).UpgradeState.NextProtocolApprovals, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolApprovals") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).UpgradeState.NextProtocolVoteBefore.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolVoteBefore") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).UpgradeState.NextProtocolSwitchOn.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "NextProtocolSwitchOn") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).UpgradeVote.UpgradePropose.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradePropose") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- bts, err = (*z).UpgradeVote.UpgradeDelay.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeDelay") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).UpgradeVote.UpgradeApprove, bts, err = msgp.ReadBoolBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "UpgradeApprove") return } } - if zb0004 > 0 { - zb0004-- + if zb0005 > 0 { + zb0005-- (*z).TxnCounter, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "TxnCounter") return } } - if zb0004 > 0 { - zb0004-- - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0007 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0007), uint64(protocol.NumStateProofTypes)) + if zb0008 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0008), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0008 { + if zb0009 { (*z).StateProofTracking = nil } else if (*z).StateProofTracking == nil { - (*z).StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0007) + (*z).StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0008) } - for zb0007 > 0 { + for zb0008 > 0 { var zb0001 protocol.StateProofType var zb0002 StateProofTrackingData - zb0007-- + zb0008-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") @@ -1546,26 +1801,26 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (*z).StateProofTracking[zb0001] = zb0002 } } - if zb0004 > 0 { - zb0004-- - var zb0009 int - var zb0010 bool - zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0005 > 0 { + zb0005-- + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0009 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0010 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0010), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0010 { + if zb0011 { (*z).ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).ParticipationUpdates.ExpiredParticipationAccounts) >= zb0009 { - (*z).ParticipationUpdates.ExpiredParticipationAccounts = ((*z).ParticipationUpdates.ExpiredParticipationAccounts)[:zb0009] + } else if (*z).ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).ParticipationUpdates.ExpiredParticipationAccounts) >= zb0010 { + (*z).ParticipationUpdates.ExpiredParticipationAccounts = ((*z).ParticipationUpdates.ExpiredParticipationAccounts)[:zb0010] } else { - (*z).ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0009) + (*z).ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0010) } for zb0003 := range (*z).ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -1575,8 +1830,37 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) } } } - if zb0004 > 0 { - err = msgp.ErrTooManyArrayFields(zb0004) + if zb0005 > 0 { + zb0005-- + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0012 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0013 { + (*z).ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).ParticipationUpdates.AbsentParticipationAccounts) >= zb0012 { + (*z).ParticipationUpdates.AbsentParticipationAccounts = ((*z).ParticipationUpdates.AbsentParticipationAccounts)[:zb0012] + } else { + (*z).ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0012) + } + for zb0004 := range (*z).ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts", zb0004) + return + } + } + } + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -1587,11 +1871,11 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err) return } - if zb0005 { + if zb0006 { (*z) = BlockHeader{} } - for zb0004 > 0 { - zb0004-- + for zb0005 > 0 { + zb0005-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -1635,14 +1919,14 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } case "gen": - var zb0011 int - zb0011, err = msgp.ReadBytesBytesHeader(bts) + var zb0014 int + zb0014, err = msgp.ReadBytesBytesHeader(bts) if err != nil { err = msgp.WrapError(err, "GenesisID") return } - if zb0011 > config.MaxGenesisIDLen { - err = msgp.ErrOverflow(uint64(zb0011), uint64(config.MaxGenesisIDLen)) + if zb0014 > config.MaxGenesisIDLen { + err = msgp.ErrOverflow(uint64(zb0014), uint64(config.MaxGenesisIDLen)) return } (*z).GenesisID, bts, err = msgp.ReadStringBytes(bts) @@ -1656,6 +1940,30 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "GenesisHash") return } + case "prp": + bts, err = (*z).Proposer.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Proposer") + return + } + case "fc": + bts, err = (*z).FeesCollected.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "FeesCollected") + return + } + case "bi": + bts, err = (*z).Bonus.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "Bonus") + return + } + case "pp": + bts, err = (*z).ProposerPayout.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "ProposerPayout") + return + } case "fees": bts, err = (*z).RewardsState.FeeSink.UnmarshalMsgWithState(bts, st) if err != nil { @@ -1747,27 +2055,27 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } case "spt": - var zb0012 int - var zb0013 bool - zb0012, zb0013, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "StateProofTracking") return } - if zb0012 > protocol.NumStateProofTypes { - err = msgp.ErrOverflow(uint64(zb0012), uint64(protocol.NumStateProofTypes)) + if zb0015 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0015), uint64(protocol.NumStateProofTypes)) err = msgp.WrapError(err, "StateProofTracking") return } - if zb0013 { + if zb0016 { (*z).StateProofTracking = nil } else if (*z).StateProofTracking == nil { - (*z).StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0012) + (*z).StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0015) } - for zb0012 > 0 { + for zb0015 > 0 { var zb0001 protocol.StateProofType var zb0002 StateProofTrackingData - zb0012-- + zb0015-- bts, err = zb0001.UnmarshalMsgWithState(bts, st) if err != nil { err = msgp.WrapError(err, "StateProofTracking") @@ -1781,24 +2089,24 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (*z).StateProofTracking[zb0001] = zb0002 } case "partupdrmv": - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0017 int + var zb0018 bool + zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0014 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0014), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0017 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0017), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0015 { + if zb0018 { (*z).ParticipationUpdates.ExpiredParticipationAccounts = nil - } else if (*z).ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).ParticipationUpdates.ExpiredParticipationAccounts) >= zb0014 { - (*z).ParticipationUpdates.ExpiredParticipationAccounts = ((*z).ParticipationUpdates.ExpiredParticipationAccounts)[:zb0014] + } else if (*z).ParticipationUpdates.ExpiredParticipationAccounts != nil && cap((*z).ParticipationUpdates.ExpiredParticipationAccounts) >= zb0017 { + (*z).ParticipationUpdates.ExpiredParticipationAccounts = ((*z).ParticipationUpdates.ExpiredParticipationAccounts)[:zb0017] } else { - (*z).ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0014) + (*z).ParticipationUpdates.ExpiredParticipationAccounts = make([]basics.Address, zb0017) } for zb0003 := range (*z).ParticipationUpdates.ExpiredParticipationAccounts { bts, err = (*z).ParticipationUpdates.ExpiredParticipationAccounts[zb0003].UnmarshalMsgWithState(bts, st) @@ -1807,6 +2115,33 @@ func (z *BlockHeader) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } + case "partupdabs": + var zb0019 int + var zb0020 bool + zb0019, zb0020, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0019 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0019), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0020 { + (*z).ParticipationUpdates.AbsentParticipationAccounts = nil + } else if (*z).ParticipationUpdates.AbsentParticipationAccounts != nil && cap((*z).ParticipationUpdates.AbsentParticipationAccounts) >= zb0019 { + (*z).ParticipationUpdates.AbsentParticipationAccounts = ((*z).ParticipationUpdates.AbsentParticipationAccounts)[:zb0019] + } else { + (*z).ParticipationUpdates.AbsentParticipationAccounts = make([]basics.Address, zb0019) + } + for zb0004 := range (*z).ParticipationUpdates.AbsentParticipationAccounts { + bts, err = (*z).ParticipationUpdates.AbsentParticipationAccounts[zb0004].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts", zb0004) + return + } + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -1830,7 +2165,7 @@ func (_ *BlockHeader) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *BlockHeader) Msgsize() (s int) { - s = 3 + 4 + (*z).Round.Msgsize() + 5 + (*z).Branch.Msgsize() + 5 + (*z).Seed.Msgsize() + 4 + (*z).TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).GenesisID) + 3 + (*z).GenesisHash.Msgsize() + 5 + (*z).RewardsState.FeeSink.Msgsize() + 4 + (*z).RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + s = 3 + 4 + (*z).Round.Msgsize() + 5 + (*z).Branch.Msgsize() + 5 + (*z).Seed.Msgsize() + 4 + (*z).TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).GenesisID) + 3 + (*z).GenesisHash.Msgsize() + 4 + (*z).Proposer.Msgsize() + 3 + (*z).FeesCollected.Msgsize() + 3 + (*z).Bonus.Msgsize() + 3 + (*z).ProposerPayout.Msgsize() + 5 + (*z).RewardsState.FeeSink.Msgsize() + 4 + (*z).RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize if (*z).StateProofTracking != nil { for zb0001, zb0002 := range (*z).StateProofTracking { _ = zb0001 @@ -1842,17 +2177,21 @@ func (z *BlockHeader) Msgsize() (s int) { for zb0003 := range (*z).ParticipationUpdates.ExpiredParticipationAccounts { s += (*z).ParticipationUpdates.ExpiredParticipationAccounts[zb0003].Msgsize() } + s += 11 + msgp.ArrayHeaderSize + for zb0004 := range (*z).ParticipationUpdates.AbsentParticipationAccounts { + s += (*z).ParticipationUpdates.AbsentParticipationAccounts[zb0004].Msgsize() + } return } // MsgIsZero returns whether this is a zero value func (z *BlockHeader) MsgIsZero() bool { - return ((*z).Round.MsgIsZero()) && ((*z).Branch.MsgIsZero()) && ((*z).Seed.MsgIsZero()) && ((*z).TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).TimeStamp == 0) && ((*z).GenesisID == "") && ((*z).GenesisHash.MsgIsZero()) && ((*z).RewardsState.FeeSink.MsgIsZero()) && ((*z).RewardsState.RewardsPool.MsgIsZero()) && ((*z).RewardsState.RewardsLevel == 0) && ((*z).RewardsState.RewardsRate == 0) && ((*z).RewardsState.RewardsResidue == 0) && ((*z).RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocolApprovals == 0) && ((*z).UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).UpgradeVote.UpgradeApprove == false) && ((*z).TxnCounter == 0) && (len((*z).StateProofTracking) == 0) && (len((*z).ParticipationUpdates.ExpiredParticipationAccounts) == 0) + return ((*z).Round.MsgIsZero()) && ((*z).Branch.MsgIsZero()) && ((*z).Seed.MsgIsZero()) && ((*z).TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).TimeStamp == 0) && ((*z).GenesisID == "") && ((*z).GenesisHash.MsgIsZero()) && ((*z).Proposer.MsgIsZero()) && ((*z).FeesCollected.MsgIsZero()) && ((*z).Bonus.MsgIsZero()) && ((*z).ProposerPayout.MsgIsZero()) && ((*z).RewardsState.FeeSink.MsgIsZero()) && ((*z).RewardsState.RewardsPool.MsgIsZero()) && ((*z).RewardsState.RewardsLevel == 0) && ((*z).RewardsState.RewardsRate == 0) && ((*z).RewardsState.RewardsResidue == 0) && ((*z).RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocolApprovals == 0) && ((*z).UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).UpgradeVote.UpgradeApprove == false) && ((*z).TxnCounter == 0) && (len((*z).StateProofTracking) == 0) && (len((*z).ParticipationUpdates.ExpiredParticipationAccounts) == 0) && (len((*z).ParticipationUpdates.AbsentParticipationAccounts) == 0) } // MaxSize returns a maximum valid message size for this message type func BlockHeaderMaxSize() (s int) { - s = 3 + 4 + basics.RoundMaxSize() + 5 + BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + s = 3 + 4 + basics.RoundMaxSize() + 5 + BlockHashMaxSize() + 5 + committee.SeedMaxSize() + 4 + crypto.DigestMaxSize() + 7 + crypto.DigestMaxSize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + config.MaxGenesisIDLen + 3 + crypto.DigestMaxSize() + 4 + basics.AddressMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 3 + basics.MicroAlgosMaxSize() + 5 + basics.AddressMaxSize() + 4 + basics.AddressMaxSize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + basics.RoundMaxSize() + 6 + protocol.ConsensusVersionMaxSize() + 10 + protocol.ConsensusVersionMaxSize() + 8 + msgp.Uint64Size + 11 + basics.RoundMaxSize() + 11 + basics.RoundMaxSize() + 12 + protocol.ConsensusVersionMaxSize() + 13 + basics.RoundMaxSize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 s += msgp.MapHeaderSize // Adding size of map keys for z.StateProofTracking s += protocol.NumStateProofTypes * (protocol.StateProofTypeMaxSize()) @@ -1861,6 +2200,9 @@ func BlockHeaderMaxSize() (s int) { s += 11 // Calculating size of slice: z.ParticipationUpdates.ExpiredParticipationAccounts s += msgp.ArrayHeaderSize + ((config.MaxProposedExpiredOnlineAccounts) * (basics.AddressMaxSize())) + s += 11 + // Calculating size of slice: z.ParticipationUpdates.AbsentParticipationAccounts + s += msgp.ArrayHeaderSize + ((config.MaxMarkAbsent) * (basics.AddressMaxSize())) return } @@ -2875,16 +3217,32 @@ func LightBlockHeaderMaxSize() (s int) { func (z *ParticipationUpdates) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0002Len := uint32(1) - var zb0002Mask uint8 /* 2 bits */ + zb0003Len := uint32(2) + var zb0003Mask uint8 /* 3 bits */ + if len((*z).AbsentParticipationAccounts) == 0 { + zb0003Len-- + zb0003Mask |= 0x2 + } if len((*z).ExpiredParticipationAccounts) == 0 { - zb0002Len-- - zb0002Mask |= 0x2 + zb0003Len-- + zb0003Mask |= 0x4 } - // variable map header, size zb0002Len - o = append(o, 0x80|uint8(zb0002Len)) - if zb0002Len != 0 { - if (zb0002Mask & 0x2) == 0 { // if not empty + // variable map header, size zb0003Len + o = append(o, 0x80|uint8(zb0003Len)) + if zb0003Len != 0 { + if (zb0003Mask & 0x2) == 0 { // if not empty + // string "partupdabs" + o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x61, 0x62, 0x73) + if (*z).AbsentParticipationAccounts == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).AbsentParticipationAccounts))) + } + for zb0002 := range (*z).AbsentParticipationAccounts { + o = (*z).AbsentParticipationAccounts[zb0002].MarshalMsg(o) + } + } + if (zb0003Mask & 0x4) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).ExpiredParticipationAccounts == nil { @@ -2914,35 +3272,35 @@ func (z *ParticipationUpdates) UnmarshalMsgWithState(bts []byte, st msgp.Unmarsh st.AllowableDepth-- var field []byte _ = field - var zb0002 int - var zb0003 bool - zb0002, zb0003, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0003 int + var zb0004 bool + zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0002, zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0002 > 0 { - zb0002-- - var zb0004 int - var zb0005 bool - zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) + if zb0003 > 0 { + zb0003-- + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0004 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0004), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0005 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0005), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "struct-from-array", "ExpiredParticipationAccounts") return } - if zb0005 { + if zb0006 { (*z).ExpiredParticipationAccounts = nil - } else if (*z).ExpiredParticipationAccounts != nil && cap((*z).ExpiredParticipationAccounts) >= zb0004 { - (*z).ExpiredParticipationAccounts = ((*z).ExpiredParticipationAccounts)[:zb0004] + } else if (*z).ExpiredParticipationAccounts != nil && cap((*z).ExpiredParticipationAccounts) >= zb0005 { + (*z).ExpiredParticipationAccounts = ((*z).ExpiredParticipationAccounts)[:zb0005] } else { - (*z).ExpiredParticipationAccounts = make([]basics.Address, zb0004) + (*z).ExpiredParticipationAccounts = make([]basics.Address, zb0005) } for zb0001 := range (*z).ExpiredParticipationAccounts { bts, err = (*z).ExpiredParticipationAccounts[zb0001].UnmarshalMsgWithState(bts, st) @@ -2952,8 +3310,37 @@ func (z *ParticipationUpdates) UnmarshalMsgWithState(bts []byte, st msgp.Unmarsh } } } - if zb0002 > 0 { - err = msgp.ErrTooManyArrayFields(zb0002) + if zb0003 > 0 { + zb0003-- + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0007 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts") + return + } + if zb0008 { + (*z).AbsentParticipationAccounts = nil + } else if (*z).AbsentParticipationAccounts != nil && cap((*z).AbsentParticipationAccounts) >= zb0007 { + (*z).AbsentParticipationAccounts = ((*z).AbsentParticipationAccounts)[:zb0007] + } else { + (*z).AbsentParticipationAccounts = make([]basics.Address, zb0007) + } + for zb0002 := range (*z).AbsentParticipationAccounts { + bts, err = (*z).AbsentParticipationAccounts[zb0002].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "AbsentParticipationAccounts", zb0002) + return + } + } + } + if zb0003 > 0 { + err = msgp.ErrTooManyArrayFields(zb0003) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -2964,11 +3351,11 @@ func (z *ParticipationUpdates) UnmarshalMsgWithState(bts []byte, st msgp.Unmarsh err = msgp.WrapError(err) return } - if zb0003 { + if zb0004 { (*z) = ParticipationUpdates{} } - for zb0002 > 0 { - zb0002-- + for zb0003 > 0 { + zb0003-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) @@ -2976,24 +3363,24 @@ func (z *ParticipationUpdates) UnmarshalMsgWithState(bts []byte, st msgp.Unmarsh } switch string(field) { case "partupdrmv": - var zb0006 int - var zb0007 bool - zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts) + var zb0009 int + var zb0010 bool + zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0006 > config.MaxProposedExpiredOnlineAccounts { - err = msgp.ErrOverflow(uint64(zb0006), uint64(config.MaxProposedExpiredOnlineAccounts)) + if zb0009 > config.MaxProposedExpiredOnlineAccounts { + err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxProposedExpiredOnlineAccounts)) err = msgp.WrapError(err, "ExpiredParticipationAccounts") return } - if zb0007 { + if zb0010 { (*z).ExpiredParticipationAccounts = nil - } else if (*z).ExpiredParticipationAccounts != nil && cap((*z).ExpiredParticipationAccounts) >= zb0006 { - (*z).ExpiredParticipationAccounts = ((*z).ExpiredParticipationAccounts)[:zb0006] + } else if (*z).ExpiredParticipationAccounts != nil && cap((*z).ExpiredParticipationAccounts) >= zb0009 { + (*z).ExpiredParticipationAccounts = ((*z).ExpiredParticipationAccounts)[:zb0009] } else { - (*z).ExpiredParticipationAccounts = make([]basics.Address, zb0006) + (*z).ExpiredParticipationAccounts = make([]basics.Address, zb0009) } for zb0001 := range (*z).ExpiredParticipationAccounts { bts, err = (*z).ExpiredParticipationAccounts[zb0001].UnmarshalMsgWithState(bts, st) @@ -3002,6 +3389,33 @@ func (z *ParticipationUpdates) UnmarshalMsgWithState(bts []byte, st msgp.Unmarsh return } } + case "partupdabs": + var zb0011 int + var zb0012 bool + zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0011 > config.MaxMarkAbsent { + err = msgp.ErrOverflow(uint64(zb0011), uint64(config.MaxMarkAbsent)) + err = msgp.WrapError(err, "AbsentParticipationAccounts") + return + } + if zb0012 { + (*z).AbsentParticipationAccounts = nil + } else if (*z).AbsentParticipationAccounts != nil && cap((*z).AbsentParticipationAccounts) >= zb0011 { + (*z).AbsentParticipationAccounts = ((*z).AbsentParticipationAccounts)[:zb0011] + } else { + (*z).AbsentParticipationAccounts = make([]basics.Address, zb0011) + } + for zb0002 := range (*z).AbsentParticipationAccounts { + bts, err = (*z).AbsentParticipationAccounts[zb0002].UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "AbsentParticipationAccounts", zb0002) + return + } + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -3029,12 +3443,16 @@ func (z *ParticipationUpdates) Msgsize() (s int) { for zb0001 := range (*z).ExpiredParticipationAccounts { s += (*z).ExpiredParticipationAccounts[zb0001].Msgsize() } + s += 11 + msgp.ArrayHeaderSize + for zb0002 := range (*z).AbsentParticipationAccounts { + s += (*z).AbsentParticipationAccounts[zb0002].Msgsize() + } return } // MsgIsZero returns whether this is a zero value func (z *ParticipationUpdates) MsgIsZero() bool { - return (len((*z).ExpiredParticipationAccounts) == 0) + return (len((*z).ExpiredParticipationAccounts) == 0) && (len((*z).AbsentParticipationAccounts) == 0) } // MaxSize returns a maximum valid message size for this message type @@ -3042,6 +3460,9 @@ func ParticipationUpdatesMaxSize() (s int) { s = 1 + 11 // Calculating size of slice: z.ExpiredParticipationAccounts s += msgp.ArrayHeaderSize + ((config.MaxProposedExpiredOnlineAccounts) * (basics.AddressMaxSize())) + s += 11 + // Calculating size of slice: z.AbsentParticipationAccounts + s += msgp.ArrayHeaderSize + ((config.MaxMarkAbsent) * (basics.AddressMaxSize())) return } diff --git a/data/datatest/impls.go b/data/datatest/impls.go index 7c9462d40d..0edae496d1 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -53,7 +53,7 @@ type entryFactoryImpl struct { } // AssembleBlock implements Ledger.AssembleBlock. -func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.ValidatedBlock, error) { +func (i entryFactoryImpl) AssembleBlock(round basics.Round, _ []basics.Address) (agreement.UnfinishedBlock, error) { prev, err := i.l.BlockHdr(round - 1) if err != nil { return nil, fmt.Errorf("could not make proposals: could not read block from ledger at round %v: %v", round, err) @@ -64,10 +64,15 @@ func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.Validated return validatedBlock{blk: &b}, nil } -// WithSeed implements the agreement.ValidatedBlock interface. -func (ve validatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { - newblock := ve.blk.WithSeed(s) - return validatedBlock{blk: &newblock} +// FinishBlock implements the agreement.UnfinishedBlock interface. +func (ve validatedBlock) FinishBlock(s committee.Seed, proposer basics.Address, eligible bool) agreement.Block { + newblock := *ve.blk + newblock.BlockHeader.Seed = s + newblock.BlockHeader.Proposer = proposer + if !eligible { + newblock.BlockHeader.ProposerPayout = basics.MicroAlgos{} + } + return agreement.Block(newblock) } // Block implements the agreement.ValidatedBlock interface. @@ -75,6 +80,11 @@ func (ve validatedBlock) Block() bookkeeping.Block { return *ve.blk } +// Round implements the agreement.UnfinishedBlock interface. +func (ve validatedBlock) Round() basics.Round { + return ve.blk.Round() +} + type ledgerImpl struct { l *data.Ledger } diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index 2eb787eafd..31ad805d68 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -87,6 +87,7 @@ type TransactionPool struct { rememberedTxids map[transactions.Txid]transactions.SignedTxn log logging.Logger + vac VotingAccountSupplier // proposalAssemblyTime is the ProposalAssemblyTime configured for this node. proposalAssemblyTime time.Duration @@ -103,12 +104,17 @@ type BlockEvaluator interface { PaySetSize() int TransactionGroup(txads []transactions.SignedTxnWithAD) error Transaction(txn transactions.SignedTxn, ad transactions.ApplyData) error - GenerateBlock() (*ledgercore.ValidatedBlock, error) + GenerateBlock(addrs []basics.Address) (*ledgercore.UnfinishedBlock, error) ResetTxnBytes() } +// VotingAccountSupplier provides a list of possible participating account addresses valid for a given round. +type VotingAccountSupplier interface { + VotingAccountsForRound(basics.Round) []basics.Address +} + // MakeTransactionPool makes a transaction pool. -func MakeTransactionPool(ledger *ledger.Ledger, cfg config.Local, log logging.Logger) *TransactionPool { +func MakeTransactionPool(ledger *ledger.Ledger, cfg config.Local, log logging.Logger, vac VotingAccountSupplier) *TransactionPool { if cfg.TxPoolExponentialIncreaseFactor < 1 { cfg.TxPoolExponentialIncreaseFactor = 1 } @@ -124,6 +130,7 @@ func MakeTransactionPool(ledger *ledger.Ledger, cfg config.Local, log logging.Lo txPoolMaxSize: cfg.TxPoolSize, proposalAssemblyTime: cfg.ProposalAssemblyTime, log: log, + vac: vac, } pool.cond.L = &pool.mu pool.assemblyCond.L = &pool.assemblyMu @@ -137,7 +144,7 @@ type poolAsmResults struct { // the ok variable indicates whether the assembly for the block roundStartedEvaluating was complete ( i.e. ok == true ) or // whether it's still in-progress. ok bool - blk *ledgercore.ValidatedBlock + blk *ledgercore.UnfinishedBlock stats telemetryspec.AssembleBlockMetrics err error // roundStartedEvaluating is the round which we were attempted to evaluate last. It's a good measure for @@ -182,6 +189,13 @@ func (pool *TransactionPool) Reset() { pool.recomputeBlockEvaluator(nil, 0) } +func (pool *TransactionPool) getVotingAccountsForRound(rnd basics.Round) []basics.Address { + if pool.vac == nil { + return nil + } + return pool.vac.VotingAccountsForRound(rnd) +} + // NumExpired returns the number of transactions that expired at the // end of a round (only meaningful if cleanup has been called for that // round). @@ -602,7 +616,7 @@ func (pool *TransactionPool) addToPendingBlockEvaluatorOnce(txgroup []transactio transactionGroupDuration := time.Since(transactionGroupStartsTime) pool.assemblyMu.Lock() defer pool.assemblyMu.Unlock() - if pool.assemblyRound > pool.pendingBlockEvaluator.Round() { + if evalRnd := pool.pendingBlockEvaluator.Round(); pool.assemblyRound > evalRnd { // the block we're assembling now isn't the one the the AssembleBlock is waiting for. While it would be really cool // to finish generating the block, it would also be pointless to spend time on it. // we're going to set the ok and assemblyCompletedOrAbandoned to "true" so we can complete this loop asap @@ -623,7 +637,7 @@ func (pool *TransactionPool) addToPendingBlockEvaluatorOnce(txgroup []transactio } blockGenerationStarts := time.Now() - lvb, gerr := pool.pendingBlockEvaluator.GenerateBlock() + lvb, gerr := pool.pendingBlockEvaluator.GenerateBlock(pool.getVotingAccountsForRound(evalRnd)) if gerr != nil { pool.assemblyResults.err = fmt.Errorf("could not generate block for %d: %v", pool.assemblyResults.roundStartedEvaluating, gerr) } else { @@ -773,11 +787,11 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transact asmStats.TransactionsLoopStartTime = int64(firstTxnGrpTime.Sub(pool.assemblyDeadline.Add(-pool.proposalAssemblyTime))) } - if !pool.assemblyResults.ok && pool.assemblyRound <= pool.pendingBlockEvaluator.Round() { + if evalRnd := pool.pendingBlockEvaluator.Round(); !pool.assemblyResults.ok && pool.assemblyRound <= evalRnd { pool.assemblyResults.ok = true pool.assemblyResults.assemblyCompletedOrAbandoned = true // this is not strictly needed, since the value would only get inspected by this go-routine, but we'll adjust it along with "ok" for consistency blockGenerationStarts := time.Now() - lvb, err := pool.pendingBlockEvaluator.GenerateBlock() + lvb, err := pool.pendingBlockEvaluator.GenerateBlock(pool.getVotingAccountsForRound(evalRnd)) if err != nil { pool.assemblyResults.err = fmt.Errorf("could not generate block for %d (end): %v", pool.assemblyResults.roundStartedEvaluating, err) } else { @@ -815,7 +829,7 @@ func (pool *TransactionPool) getStateProofStats(txib *transactions.SignedTxnInBl // AssembleBlock assembles a block for a given round, trying not to // take longer than deadline to finish. -func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Time) (assembled *ledgercore.ValidatedBlock, err error) { +func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Time) (assembled *ledgercore.UnfinishedBlock, err error) { var stats telemetryspec.AssembleBlockMetrics if pool.logAssembleStats { @@ -829,7 +843,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim dt := time.Since(start) stats.Nanoseconds = dt.Nanoseconds() - payset := assembled.Block().Payset + payset := assembled.UnfinishedBlock().Payset if len(payset) != 0 { totalFees := uint64(0) @@ -864,7 +878,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim } stats.AverageFee = totalFees / uint64(stats.IncludedCount) } - stats.StateProofNextRound = uint64(assembled.Block().StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + stats.StateProofNextRound = uint64(assembled.UnfinishedBlock().StateProofTracking[protocol.StateProofBasic].StateProofNextRound) var details struct { Round uint64 } @@ -896,6 +910,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim pool.assemblyDeadline = deadline pool.assemblyRound = round + for time.Now().Before(deadline) && (!pool.assemblyResults.ok || pool.assemblyResults.roundStartedEvaluating != round) { condvar.TimedWait(&pool.assemblyCond, time.Until(deadline)) } @@ -958,7 +973,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim // assembleEmptyBlock construct a new block for the given round. Internally it's using the ledger database calls, so callers // need to be aware that it might take a while before it would return. -func (pool *TransactionPool) assembleEmptyBlock(round basics.Round) (assembled *ledgercore.ValidatedBlock, err error) { +func (pool *TransactionPool) assembleEmptyBlock(round basics.Round) (assembled *ledgercore.UnfinishedBlock, err error) { prevRound := round - 1 prev, err := pool.ledger.BlockHdr(prevRound) if err != nil { @@ -979,11 +994,11 @@ func (pool *TransactionPool) assembleEmptyBlock(round basics.Round) (assembled * err = fmt.Errorf("TransactionPool.assembleEmptyBlock: cannot start evaluator for %d: %w", round, err) return nil, err } - return blockEval.GenerateBlock() + return blockEval.GenerateBlock(pool.getVotingAccountsForRound(round)) } // AssembleDevModeBlock assemble a new block from the existing transaction pool. The pending evaluator is being -func (pool *TransactionPool) AssembleDevModeBlock() (assembled *ledgercore.ValidatedBlock, err error) { +func (pool *TransactionPool) AssembleDevModeBlock() (assembled *ledgercore.UnfinishedBlock, err error) { pool.mu.Lock() defer pool.mu.Unlock() diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index 4229b0510d..72c757aa2d 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -168,7 +168,7 @@ func TestMinBalanceOK(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) // sender goes below min tx := transactions.Transaction{ @@ -211,7 +211,7 @@ func TestSenderGoesBelowMinBalance(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) // sender goes below min tx := transactions.Transaction{ @@ -255,7 +255,7 @@ func TestSenderGoesBelowMinBalanceDueToAssets(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) assetTx := transactions.Transaction{ Type: protocol.AssetConfigTx, @@ -326,7 +326,7 @@ func TestCloseAccount(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) // sender goes below min closeTx := transactions.Transaction{ @@ -389,7 +389,7 @@ func TestCloseAccountWhileTxIsPending(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) // sender goes below min tx := transactions.Transaction{ @@ -453,7 +453,7 @@ func TestClosingAccountBelowMinBalance(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) // sender goes below min closeTx := transactions.Transaction{ @@ -497,7 +497,7 @@ func TestRecipientGoesBelowMinBalance(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) // sender goes below min tx := transactions.Transaction{ @@ -538,7 +538,7 @@ func TestRememberForget(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(mockLedger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(mockLedger, cfg, logging.Base(), nil) eval := newBlockEvaluator(t, mockLedger) @@ -574,10 +574,11 @@ func TestRememberForget(t *testing.T) { numberOfTxns := numOfAccounts*numOfAccounts - numOfAccounts require.Len(t, pending, numberOfTxns) - blk, err := eval.GenerateBlock() + ufblk, err := eval.GenerateBlock(nil) require.NoError(t, err) - err = mockLedger.AddValidatedBlock(*blk, agreement.Certificate{}) + blk := ledgercore.MakeValidatedBlock(ufblk.UnfinishedBlock(), ufblk.UnfinishedDeltas()) + err = mockLedger.AddValidatedBlock(blk, agreement.Certificate{}) require.NoError(t, err) transactionPool.OnNewBlock(blk.Block(), ledgercore.StateDelta{}) @@ -605,7 +606,7 @@ func TestCleanUp(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(mockLedger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(mockLedger, cfg, logging.Base(), nil) issuedTransactions := 0 for i, sender := range addresses { @@ -637,10 +638,11 @@ func TestCleanUp(t *testing.T) { for mockLedger.Latest() < 6 { eval := newBlockEvaluator(t, mockLedger) - blk, err := eval.GenerateBlock() + ufblk, err := eval.GenerateBlock(nil) require.NoError(t, err) - err = mockLedger.AddValidatedBlock(*blk, agreement.Certificate{}) + blk := ledgercore.MakeValidatedBlock(ufblk.UnfinishedBlock(), ufblk.UnfinishedDeltas()) + err = mockLedger.AddValidatedBlock(blk, agreement.Certificate{}) require.NoError(t, err) transactionPool.OnNewBlock(blk.Block(), ledgercore.StateDelta{}) @@ -653,10 +655,11 @@ func TestCleanUp(t *testing.T) { for mockLedger.Latest() < 6+basics.Round(expiredHistory*proto.MaxTxnLife) { eval := newBlockEvaluator(t, mockLedger) - blk, err := eval.GenerateBlock() + ufblk, err := eval.GenerateBlock(nil) require.NoError(t, err) - err = mockLedger.AddValidatedBlock(*blk, agreement.Certificate{}) + blk := ledgercore.MakeValidatedBlock(ufblk.UnfinishedBlock(), ufblk.UnfinishedDeltas()) + err = mockLedger.AddValidatedBlock(blk, agreement.Certificate{}) require.NoError(t, err) transactionPool.OnNewBlock(blk.Block(), ledgercore.StateDelta{}) @@ -684,7 +687,7 @@ func TestFixOverflowOnNewBlock(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(mockLedger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(mockLedger, cfg, logging.Base(), nil) overSpender := addresses[0] var overSpenderAmount uint64 @@ -748,10 +751,11 @@ func TestFixOverflowOnNewBlock(t *testing.T) { require.NoError(t, err) // simulate this transaction was applied - block, err := blockEval.GenerateBlock() + ufblk, err := blockEval.GenerateBlock(nil) require.NoError(t, err) - err = mockLedger.AddValidatedBlock(*block, agreement.Certificate{}) + block := ledgercore.MakeValidatedBlock(ufblk.UnfinishedBlock(), ufblk.UnfinishedDeltas()) + err = mockLedger.AddValidatedBlock(block, agreement.Certificate{}) require.NoError(t, err) transactionPool.OnNewBlock(block.Block(), ledgercore.StateDelta{}) @@ -781,7 +785,7 @@ func TestOverspender(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) receiver := addresses[1] tx := transactions.Transaction{ @@ -843,7 +847,7 @@ func TestRemove(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) sender := addresses[0] receiver := addresses[1] @@ -900,7 +904,7 @@ func TestLogicSigOK(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) // sender goes below min tx := transactions.Transaction{ @@ -946,7 +950,7 @@ func TestTransactionPool_CurrentFeePerByte(t *testing.T) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize * 15 cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(l, cfg, logging.Base()) + transactionPool := MakeTransactionPool(l, cfg, logging.Base(), nil) for i, sender := range addresses { for j := 0; j < testPoolSize*15/len(addresses); j++ { @@ -997,7 +1001,7 @@ func BenchmarkTransactionPoolRememberOne(b *testing.B) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = b.N cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) signedTransactions := make([]transactions.SignedTxn, 0, b.N) for i, sender := range addresses { for j := 0; j < b.N/len(addresses); j++ { @@ -1029,7 +1033,7 @@ func BenchmarkTransactionPoolRememberOne(b *testing.B) { b.StopTimer() b.ResetTimer() ledger = makeMockLedger(b, initAccFixed(addresses, 1<<32)) - transactionPool = MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool = MakeTransactionPool(ledger, cfg, logging.Base(), nil) b.StartTimer() for _, signedTx := range signedTransactions { @@ -1058,7 +1062,7 @@ func BenchmarkTransactionPoolPending(b *testing.B) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = benchPoolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) var block bookkeeping.Block block.Payset = make(transactions.Payset, 0) @@ -1136,7 +1140,7 @@ func BenchmarkTransactionPoolRecompute(b *testing.B) { cfg.EnableProcessBlockStats = false setupPool := func() (*TransactionPool, map[transactions.Txid]ledgercore.IncludedTransactions, uint) { - transactionPool := MakeTransactionPool(l, cfg, logging.Base()) + transactionPool := MakeTransactionPool(l, cfg, logging.Base(), nil) // make some transactions var signedTransactions []transactions.SignedTxn @@ -1227,7 +1231,7 @@ func BenchmarkTransactionPoolSteadyState(b *testing.B) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = poolSize cfg.EnableProcessBlockStats = false - transactionPool := MakeTransactionPool(l, cfg, logging.Base()) + transactionPool := MakeTransactionPool(l, cfg, logging.Base(), nil) var signedTransactions []transactions.SignedTxn for i := 0; i < b.N; i++ { @@ -1290,10 +1294,11 @@ func BenchmarkTransactionPoolSteadyState(b *testing.B) { ledgerTxnQueue = ledgerTxnQueue[1:] } - blk, err := eval.GenerateBlock() + ufblk, err := eval.GenerateBlock(nil) require.NoError(b, err) - err = l.AddValidatedBlock(*blk, agreement.Certificate{}) + blk := ledgercore.MakeValidatedBlock(ufblk.UnfinishedBlock(), ufblk.UnfinishedDeltas()) + err = l.AddValidatedBlock(blk, agreement.Certificate{}) require.NoError(b, err) transactionPool.OnNewBlock(blk.Block(), ledgercore.StateDelta{}) @@ -1324,7 +1329,7 @@ func TestTxPoolSizeLimits(t *testing.T) { ledger := makeMockLedger(t, initAcc(map[basics.Address]uint64{firstAddress: proto.MinBalance + 2*proto.MinTxnFee*uint64(cfg.TxPoolSize)})) - transactionPool := MakeTransactionPool(ledger, cfg, logging.Base()) + transactionPool := MakeTransactionPool(ledger, cfg, logging.Base(), nil) receiver := addresses[1] @@ -1439,7 +1444,7 @@ func TestStateProofLogging(t *testing.T) { // Set the ledger and the transaction pool mockLedger := makeMockLedger(t, initAccounts) - transactionPool := MakeTransactionPool(mockLedger, cfg, logger) + transactionPool := MakeTransactionPool(mockLedger, cfg, logger, nil) transactionPool.logAssembleStats = true // Set the first round block @@ -1458,10 +1463,11 @@ func TestStateProofLogging(t *testing.T) { // Simulate the blocks up to round 512 without any transactions for i := 1; true; i++ { - blk, err := transactionPool.AssembleBlock(basics.Round(i), time.Time{}) + ufblk, err := transactionPool.AssembleBlock(basics.Round(i), time.Time{}) require.NoError(t, err) - err = mockLedger.AddValidatedBlock(*blk, agreement.Certificate{}) + blk := ledgercore.MakeValidatedBlock(ufblk.UnfinishedBlock(), ufblk.UnfinishedDeltas()) + err = mockLedger.AddValidatedBlock(blk, agreement.Certificate{}) require.NoError(t, err) // Move to the next round diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 3f609fd7ee..ec5537a701 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1707,6 +1707,12 @@ block BlkSeed global AssetCreateMinBalance global AssetOptInMinBalance global GenesisHash +pushint 1 +block BlkProposer +pushint 1 +block BlkFeesCollected +pushint 1 +block BlkBonus `, AssemblerMaxVersion) for _, names := range [][]string{GlobalFieldNames[:], TxnFieldNames[:], blockFieldNames[:]} { for _, f := range names { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 2bf6f2f272..3008250a54 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -5701,17 +5701,21 @@ func opBlock(cx *EvalContext) error { switch fs.field { case BlkSeed: cx.Stack[last].Bytes = hdr.Seed[:] - return nil case BlkTimestamp: - cx.Stack[last].Bytes = nil if hdr.TimeStamp < 0 { return fmt.Errorf("block(%d) timestamp %d < 0", round, hdr.TimeStamp) } - cx.Stack[last].Uint = uint64(hdr.TimeStamp) - return nil + cx.Stack[last] = stackValue{Uint: uint64(hdr.TimeStamp)} + case BlkProposer: + cx.Stack[last].Bytes = hdr.Proposer[:] + case BlkFeesCollected: + cx.Stack[last] = stackValue{Uint: hdr.FeesCollected.Raw} + case BlkBonus: + cx.Stack[last] = stackValue{Uint: hdr.Bonus.Raw} default: return fmt.Errorf("invalid block field %s", fs.field) } + return nil } // pcDetails return PC and disassembled instructions at PC up to 2 opcodes back diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index a2f971f7b7..d0603d0ffc 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -965,6 +965,12 @@ const ( BlkSeed BlockField = iota // BlkTimestamp is the Block's timestamp, seconds from epoch BlkTimestamp + // BlkProposer is the Block's proposer, or ZeroAddress, pre Payouts.Enabled + BlkProposer + // BlkFeesCollected is the sum of fees for the block, or 0, pre Payouts.Enabled + BlkFeesCollected + // BlkBonus is the extra amount to be paid for the given block (from FeeSink) + BlkBonus invalidBlockField // compile-time constant for number of fields ) @@ -980,6 +986,9 @@ type blockFieldSpec struct { var blockFieldSpecs = [...]blockFieldSpec{ {BlkSeed, StackBytes, randomnessVersion}, {BlkTimestamp, StackUint64, randomnessVersion}, + {BlkProposer, StackAddress, incentiveVersion}, + {BlkFeesCollected, StackUint64, incentiveVersion}, + {BlkBonus, StackUint64, incentiveVersion}, } func blockFieldSpecByField(r BlockField) (blockFieldSpec, bool) { diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 37bfeb9bcc..5b92357909 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -352,12 +352,15 @@ func _() { var x [1]struct{} _ = x[BlkSeed-0] _ = x[BlkTimestamp-1] - _ = x[invalidBlockField-2] + _ = x[BlkProposer-2] + _ = x[BlkFeesCollected-3] + _ = x[BlkBonus-4] + _ = x[invalidBlockField-5] } -const _BlockField_name = "BlkSeedBlkTimestampinvalidBlockField" +const _BlockField_name = "BlkSeedBlkTimestampBlkProposerBlkFeesCollectedBlkBonusinvalidBlockField" -var _BlockField_index = [...]uint8{0, 7, 19, 36} +var _BlockField_index = [...]uint8{0, 7, 19, 30, 46, 54, 71} func (i BlockField) String() string { if i < 0 || i >= BlockField(len(_BlockField_index)-1) { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index b8ecd76cac..ae152bf919 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -71,11 +71,13 @@ const fpVersion = 8 // changes for frame pointers and simpler function d const sharedResourcesVersion = 9 // apps can access resources from other transactions. +const pairingVersion = 10 // bn256 opcodes. will add bls12-381, and unify the available opcodes. +const spliceVersion = 10 // box splicing/resizing + // EXPERIMENTAL. These should be revisited whenever a new LogicSigVersion is // moved from vFuture to a new consensus version. If they remain unready, bump // their version, and fixup TestAssemble() in assembler_test.go. -const pairingVersion = 10 // bn256 opcodes. will add bls12-381, and unify the available opcodes. -const spliceVersion = 10 // box splicing/resizing +const incentiveVersion = 11 // block fields, heartbeat const spOpcodesVersion = 11 // falcon_verify, sumhash512 diff --git a/data/transactions/payment.go b/data/transactions/payment.go index 62eafdb1e9..d51e4ed1d6 100644 --- a/data/transactions/payment.go +++ b/data/transactions/payment.go @@ -37,17 +37,21 @@ type PaymentTxnFields struct { CloseRemainderTo basics.Address `codec:"close"` } -func (payment PaymentTxnFields) checkSpender(header Header, spec SpecialAddresses, proto config.ConsensusParams) error { +// CheckSpender performs some stateless checks on the Sender of a pay transaction +func (payment PaymentTxnFields) CheckSpender(header Header, spec SpecialAddresses, proto config.ConsensusParams) error { if header.Sender == payment.CloseRemainderTo { return fmt.Errorf("transaction cannot close account to its sender %v", header.Sender) } - // the FeeSink account may only spend to the IncentivePool + // the FeeSink account may only spend to the IncentivePool (not at all, if Payouts.Enabled) if header.Sender == spec.FeeSink { + if proto.Payouts.Enabled { + return fmt.Errorf("cannot spend from fee sink address %v", header.Sender) + } if payment.Receiver != spec.RewardsPool { return fmt.Errorf("cannot spend from fee sink's address %v to non incentive pool address %v", header.Sender, payment.Receiver) } - if payment.CloseRemainderTo != (basics.Address{}) { + if !payment.CloseRemainderTo.IsZero() { return fmt.Errorf("cannot close fee sink %v to %v", header.Sender, payment.CloseRemainderTo) } } diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index b0bb7c413b..04a258633f 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -352,7 +352,7 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa switch tx.Type { case protocol.PaymentTx: // in case that the fee sink is spending, check that this spend is to a valid address - err := tx.checkSpender(tx.Header, spec, proto) + err := tx.CheckSpender(tx.Header, spec, proto) if err != nil { return err } @@ -365,8 +365,8 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa } // The trio of [VotePK, SelectionPK, VoteKeyDilution] needs to be all zeros or all non-zero for the transaction to be valid. - if !((tx.KeyregTxnFields.VotePK == crypto.OneTimeSignatureVerifier{} && tx.KeyregTxnFields.SelectionPK == crypto.VRFVerifier{} && tx.KeyregTxnFields.VoteKeyDilution == 0) || - (tx.KeyregTxnFields.VotePK != crypto.OneTimeSignatureVerifier{} && tx.KeyregTxnFields.SelectionPK != crypto.VRFVerifier{} && tx.KeyregTxnFields.VoteKeyDilution != 0)) { + if !((tx.KeyregTxnFields.VotePK.IsEmpty() && tx.KeyregTxnFields.SelectionPK.IsEmpty() && tx.KeyregTxnFields.VoteKeyDilution == 0) || + (!tx.KeyregTxnFields.VotePK.IsEmpty() && !tx.KeyregTxnFields.SelectionPK.IsEmpty() && tx.KeyregTxnFields.VoteKeyDilution != 0)) { return errKeyregTxnNonCoherentVotingKeys } @@ -395,7 +395,7 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa // that type of transaction, it is invalid. return errKeyregTxnUnsupportedSwitchToNonParticipating } - suppliesNullKeys := tx.KeyregTxnFields.VotePK == crypto.OneTimeSignatureVerifier{} || tx.KeyregTxnFields.SelectionPK == crypto.VRFVerifier{} + suppliesNullKeys := tx.KeyregTxnFields.VotePK.IsEmpty() || tx.KeyregTxnFields.SelectionPK.IsEmpty() if !suppliesNullKeys { return errKeyregTxnGoingOnlineWithNonParticipating } @@ -673,7 +673,7 @@ func (tx Transaction) stateProofPKWellFormed(proto config.ConsensusParams) error return nil } - if tx.VotePK == (crypto.OneTimeSignatureVerifier{}) || tx.SelectionPK == (crypto.VRFVerifier{}) { + if tx.VotePK.IsEmpty() || tx.SelectionPK.IsEmpty() { if !isEmpty { return errKeyregTxnOfflineShouldBeEmptyStateProofPK } diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index e3a25619bb..08dd145a8c 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -101,13 +101,15 @@ func TestGoOnlineGoNonparticipatingContradiction(t *testing.T) { tx.KeyregTxnFields = KeyregTxnFields{ VotePK: v.OneTimeSignatureVerifier, SelectionPK: vrf.PK, + VoteKeyDilution: 1, + VoteFirst: 1, + VoteLast: 100, Nonparticipation: true, } // this tx tries to both register keys to go online, and mark an account as non-participating. // it is not well-formed. - feeSink := basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb1, 0x75, 0x76, 0xbd, 0x45, 0x9a, 0xe6, 0x42, 0x1d, 0x48, 0x6d, 0xa3, 0xd4, 0xef, 0x22, 0x47, 0xc4, 0x9, 0xa3, 0x96, 0xb8, 0x2e, 0xa2, 0x21} - err = tx.WellFormed(SpecialAddresses{FeeSink: feeSink}, config.Consensus[protocol.ConsensusCurrentVersion]) - require.Error(t, err) + err = tx.WellFormed(SpecialAddresses{}, config.Consensus[protocol.ConsensusCurrentVersion]) + require.ErrorContains(t, err, "tries to register keys to go online, but nonparticipatory flag is set") } func TestGoNonparticipatingWellFormed(t *testing.T) { @@ -125,19 +127,17 @@ func TestGoNonparticipatingWellFormed(t *testing.T) { } // this tx is well-formed - feeSink := basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb1, 0x75, 0x76, 0xbd, 0x45, 0x9a, 0xe6, 0x42, 0x1d, 0x48, 0x6d, 0xa3, 0xd4, 0xef, 0x22, 0x47, 0xc4, 0x9, 0xa3, 0x96, 0xb8, 0x2e, 0xa2, 0x21} - err = tx.WellFormed(SpecialAddresses{FeeSink: feeSink}, curProto) + err = tx.WellFormed(SpecialAddresses{}, curProto) require.NoError(t, err) // but it should stop being well-formed if the protocol does not support it curProto.SupportBecomeNonParticipatingTransactions = false - err = tx.WellFormed(SpecialAddresses{FeeSink: feeSink}, curProto) - require.Error(t, err) + err = tx.WellFormed(SpecialAddresses{}, curProto) + require.ErrorContains(t, err, "mark an account as nonparticipating, but") } func TestAppCallCreateWellFormed(t *testing.T) { partitiontest.PartitionTest(t) - feeSink := basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb1, 0x75, 0x76, 0xbd, 0x45, 0x9a, 0xe6, 0x42, 0x1d, 0x48, 0x6d, 0xa3, 0xd4, 0xef, 0x22, 0x47, 0xc4, 0x9, 0xa3, 0x96, 0xb8, 0x2e, 0xa2, 0x21} curProto := config.Consensus[protocol.ConsensusCurrentVersion] futureProto := config.Consensus[protocol.ConsensusFuture] addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA") @@ -253,7 +253,7 @@ func TestAppCallCreateWellFormed(t *testing.T) { } for i, usecase := range usecases { t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { - err := usecase.tx.WellFormed(SpecialAddresses{FeeSink: feeSink}, usecase.proto) + err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto) if usecase.expectedError != "" { require.Error(t, err) require.Contains(t, err.Error(), usecase.expectedError) @@ -267,8 +267,6 @@ func TestAppCallCreateWellFormed(t *testing.T) { func TestWellFormedErrors(t *testing.T) { partitiontest.PartitionTest(t) - feeSink := basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb1, 0x75, 0x76, 0xbd, 0x45, 0x9a, 0xe6, 0x42, 0x1d, 0x48, 0x6d, 0xa3, 0xd4, 0xef, 0x22, 0x47, 0xc4, 0x9, 0xa3, 0x96, 0xb8, 0x2e, 0xa2, 0x21} - specialAddr := SpecialAddresses{FeeSink: feeSink} curProto := config.Consensus[protocol.ConsensusCurrentVersion] futureProto := config.Consensus[protocol.ConsensusFuture] protoV27 := config.Consensus[protocol.ConsensusV27] @@ -595,7 +593,7 @@ func TestWellFormedErrors(t *testing.T) { }, } for _, usecase := range usecases { - err := usecase.tx.WellFormed(specialAddr, usecase.proto) + err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto) require.Equal(t, usecase.expectedError, err) } } @@ -632,14 +630,12 @@ func TestWellFormedKeyRegistrationTx(t *testing.T) { tx := generateDummyGoNonparticpatingTransaction(addr) curProto := config.Consensus[protocol.ConsensusCurrentVersion] - feeSink := basics.Address{0x7, 0xda, 0xcb, 0x4b, 0x6d, 0x9e, 0xd1, 0x41, 0xb1, 0x75, 0x76, 0xbd, 0x45, 0x9a, 0xe6, 0x42, 0x1d, 0x48, 0x6d, 0xa3, 0xd4, 0xef, 0x22, 0x47, 0xc4, 0x9, 0xa3, 0x96, 0xb8, 0x2e, 0xa2, 0x21} - spec := SpecialAddresses{FeeSink: feeSink} if !curProto.SupportBecomeNonParticipatingTransactions { t.Skipf("Skipping rest of test because current protocol version %v does not support become-nonparticipating transactions", protocol.ConsensusCurrentVersion) } // this tx is well-formed - err = tx.WellFormed(spec, curProto) + err = tx.WellFormed(SpecialAddresses{}, curProto) require.NoError(t, err) type keyRegTestCase struct { @@ -677,7 +673,7 @@ func TestWellFormedKeyRegistrationTx(t *testing.T) { curProto.EnableKeyregCoherencyCheck = testCase.enableKeyregCoherencyCheck curProto.EnableStateProofKeyregCheck = testCase.enableStateProofKeyregCheck curProto.MaxKeyregValidPeriod = maxValidPeriod // TODO: remove this when MaxKeyregValidPeriod is in CurrentVersion - return tx.WellFormed(spec, curProto) + return tx.WellFormed(SpecialAddresses{}, curProto) } if *generateFlag == true { diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 3f567554bf..5eb40741ee 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -820,7 +820,7 @@ func makeTestTxHandlerOrphanedWithContext(ctx context.Context, backlogSize int, } func makeTestTxHandler(dl *Ledger, cfg config.Local) (*TxHandler, error) { - tp := pools.MakeTransactionPool(dl.Ledger, cfg, logging.Base()) + tp := pools.MakeTransactionPool(dl.Ledger, cfg, logging.Base(), nil) backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) opts := TxHandlerOpts{ tp, backlogPool, dl, &mocks.MockNetwork{}, "", crypto.Digest{}, cfg, diff --git a/data/txntest/txn.go b/data/txntest/txn.go index 515c9df458..aea4de005b 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -23,6 +23,7 @@ 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/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/stateproofmsg" @@ -55,6 +56,7 @@ type Txn struct { VoteLast basics.Round VoteKeyDilution uint64 Nonparticipation bool + StateProofPK merklesignature.Commitment Receiver basics.Address Amount uint64 @@ -147,30 +149,39 @@ func (tx *Txn) FillDefaults(params config.ConsensusParams) { tx.LastValid = tx.FirstValid + basics.Round(params.MaxTxnLife) } - if tx.Type == protocol.ApplicationCallTx && - (tx.ApplicationID == 0 || tx.OnCompletion == transactions.UpdateApplicationOC) { - - switch program := tx.ApprovalProgram.(type) { - case nil: - tx.ApprovalProgram = fmt.Sprintf("#pragma version %d\nint 1", params.LogicSigVersion) - case string: - if program != "" && !strings.Contains(program, "#pragma version") { - pragma := fmt.Sprintf("#pragma version %d\n", params.LogicSigVersion) - tx.ApprovalProgram = pragma + program + switch tx.Type { + case protocol.KeyRegistrationTx: + if !tx.VotePK.MsgIsZero() && !tx.SelectionPK.MsgIsZero() { + if tx.VoteLast == 0 { + tx.VoteLast = tx.VoteFirst + 1_000_000 } - case []byte: } + case protocol.ApplicationCallTx: + // fill in empty programs + if tx.ApplicationID == 0 || tx.OnCompletion == transactions.UpdateApplicationOC { + switch program := tx.ApprovalProgram.(type) { + case nil: + tx.ApprovalProgram = fmt.Sprintf("#pragma version %d\nint 1", params.LogicSigVersion) + case string: + if program != "" && !strings.Contains(program, "#pragma version") { + pragma := fmt.Sprintf("#pragma version %d\n", params.LogicSigVersion) + tx.ApprovalProgram = pragma + program + } + case []byte: + } - switch program := tx.ClearStateProgram.(type) { - case nil: - tx.ClearStateProgram = tx.ApprovalProgram - case string: - if program != "" && !strings.Contains(program, "#pragma version") { - pragma := fmt.Sprintf("#pragma version %d\n", params.LogicSigVersion) - tx.ClearStateProgram = pragma + program + switch program := tx.ClearStateProgram.(type) { + case nil: + tx.ClearStateProgram = tx.ApprovalProgram + case string: + if program != "" && !strings.Contains(program, "#pragma version") { + pragma := fmt.Sprintf("#pragma version %d\n", params.LogicSigVersion) + tx.ClearStateProgram = pragma + program + } + case []byte: } - case []byte: } + } } @@ -228,6 +239,7 @@ func (tx Txn) Txn() transactions.Transaction { VoteLast: tx.VoteLast, VoteKeyDilution: tx.VoteKeyDilution, Nonparticipation: tx.Nonparticipation, + StateProofPK: tx.StateProofPK, }, PaymentTxnFields: transactions.PaymentTxnFields{ Receiver: tx.Receiver, diff --git a/ledger/acctdeltas.go b/ledger/acctdeltas.go index d6bb91881f..ad0be650b7 100644 --- a/ledger/acctdeltas.go +++ b/ledger/acctdeltas.go @@ -986,18 +986,34 @@ func onlineAccountsNewRoundImpl( newAcct := data.newAcct[j] updRound := data.updRound[j] newStatus := data.newStatus[j] - if prevAcct.Ref == nil { - // zero rowid means we don't have a previous value. - if newAcct.IsEmpty() { - // IsEmpty means we don't have a previous value. - // if we didn't had it before, and we don't have anything now, just skip it. - } else { - if newStatus == basics.Online { - if newAcct.IsVotingEmpty() { - err = fmt.Errorf("empty voting data for online account %s: %v", data.address.String(), newAcct) - return nil, err - } - // create a new entry. + if newStatus == basics.Online && newAcct.IsVotingEmpty() { + return nil, fmt.Errorf("empty voting data for online account %s: %v", data.address, newAcct) + } + if prevAcct.Ref == nil { // zero rowid (nil Ref) means we don't have a previous value. + if newStatus != basics.Online { + continue // didn't exist, and not going online, we don't care. + } + + // create a new entry. + var ref trackerdb.OnlineAccountRef + normBalance := newAcct.NormalizedOnlineBalance(proto) + ref, err = writer.InsertOnlineAccount(data.address, normBalance, newAcct, updRound, uint64(newAcct.VoteLastValid)) + if err != nil { + return nil, err + } + updated := trackerdb.PersistedOnlineAccountData{ + Addr: data.address, + AccountData: newAcct, + Round: lastUpdateRound, + Ref: ref, + UpdRound: basics.Round(updRound), + } + updatedAccounts = append(updatedAccounts, updated) + prevAcct = updated + } else { // non-zero rowid (non-nil Ref) means we had a previous value. + if newStatus == basics.Online { + // was already online, so create an update only if something changed + if prevAcct.AccountData != newAcct { var ref trackerdb.OnlineAccountRef normBalance := newAcct.NormalizedOnlineBalance(proto) ref, err = writer.InsertOnlineAccount(data.address, normBalance, newAcct, updRound, uint64(newAcct.VoteLastValid)) @@ -1011,21 +1027,12 @@ func onlineAccountsNewRoundImpl( Ref: ref, UpdRound: basics.Round(updRound), } + updatedAccounts = append(updatedAccounts, updated) prevAcct = updated - } else if !newAcct.IsVotingEmpty() { - err = fmt.Errorf("non-empty voting data for non-online account %s: %v", data.address.String(), newAcct) - return nil, err - } - } - } else { - // non-zero rowid means we had a previous value. - if newAcct.IsVotingEmpty() { - // new value is zero then go offline - if newStatus == basics.Online { - err = fmt.Errorf("empty voting data but online account %s: %v", data.address.String(), newAcct) - return nil, err } + } else { + // "delete" by inserting a zero entry var ref trackerdb.OnlineAccountRef ref, err = writer.InsertOnlineAccount(data.address, 0, trackerdb.BaseOnlineAccountData{}, updRound, 0) if err != nil { @@ -1041,25 +1048,6 @@ func onlineAccountsNewRoundImpl( updatedAccounts = append(updatedAccounts, updated) prevAcct = updated - } else { - if prevAcct.AccountData != newAcct { - var ref trackerdb.OnlineAccountRef - normBalance := newAcct.NormalizedOnlineBalance(proto) - ref, err = writer.InsertOnlineAccount(data.address, normBalance, newAcct, updRound, uint64(newAcct.VoteLastValid)) - if err != nil { - return nil, err - } - updated := trackerdb.PersistedOnlineAccountData{ - Addr: data.address, - AccountData: newAcct, - Round: lastUpdateRound, - Ref: ref, - UpdRound: basics.Round(updRound), - } - - updatedAccounts = append(updatedAccounts, updated) - prevAcct = updated - } } } } diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index ec00269dbe..a3e5bd49db 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -2621,20 +2621,21 @@ func TestAccountOnlineAccountsNewRound(t *testing.T) { deltaC.newAcct[0].VoteFirstValid = 0 updates.deltas = []onlineAccountDelta{deltaC} _, err = onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound) - require.Error(t, err) + require.ErrorContains(t, err, "empty voting data for online account") - // check errors: new non-online with non-empty voting data + // It used to be an error to go offline with non-empty voting data, but + // account suspension makes it legal. deltaB.newStatus[0] = basics.Offline deltaB.newAcct[0].VoteFirstValid = 1 updates.deltas = []onlineAccountDelta{deltaB} _, err = onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound) - require.Error(t, err) + require.NoError(t, err) // check errors: new online with empty voting data deltaD.newStatus[0] = basics.Online updates.deltas = []onlineAccountDelta{deltaD} _, err = onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound) - require.Error(t, err) + require.ErrorContains(t, err, "empty voting data for online account") } func TestAccountOnlineAccountsNewRoundFlip(t *testing.T) { @@ -2937,8 +2938,7 @@ func TestOnlineAccountsNewRoundError(t *testing.T) { updates.deltas = append(updates.deltas, deltaA) lastUpdateRound := basics.Round(1) updated, err := onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound) - require.Error(t, err) - require.Equal(t, errMockOnlineAccountsErrorWriter, err) + require.ErrorIs(t, err, errMockOnlineAccountsErrorWriter) require.Empty(t, updated) // update acct A => exercise "update" @@ -2965,8 +2965,7 @@ func TestOnlineAccountsNewRoundError(t *testing.T) { updates.deltas = append(updates.deltas, deltaA2) lastUpdateRound = basics.Round(3) updated, err = onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound) - require.Error(t, err) - require.Equal(t, errMockOnlineAccountsErrorWriter, err) + require.ErrorIs(t, err, errMockOnlineAccountsErrorWriter) require.Empty(t, updated) // make acct A offline => exercise "deletion" @@ -2993,8 +2992,7 @@ func TestOnlineAccountsNewRoundError(t *testing.T) { updates.deltas = append(updates.deltas, deltaA3) lastUpdateRound = basics.Round(4) updated, err = onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound) - require.Error(t, err) - require.Equal(t, errMockOnlineAccountsErrorWriter, err) + require.ErrorIs(t, err, errMockOnlineAccountsErrorWriter) require.Empty(t, updated) } @@ -3200,8 +3198,7 @@ func TestAccountsNewRoundError(t *testing.T) { } lastUpdateRound := basics.Round(i + 1) updatedAcct, updatedResources, updatedKvs, err := accountsNewRoundImpl(writer, updates, resources, kvs, creatables, proto, lastUpdateRound) - require.Error(t, err) - require.Equal(t, test.expErr, err) + require.ErrorIs(t, err, test.expErr) require.Empty(t, updatedAcct) require.Empty(t, updatedResources) require.Empty(t, updatedKvs) diff --git a/ledger/acctonline.go b/ledger/acctonline.go index cdccc9a9e1..f204681abe 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -630,6 +630,7 @@ func (ao *onlineAccounts) LookupOnlineAccountData(rnd basics.Round, addr basics. data.VotingData.VoteFirstValid = oad.VotingData.VoteFirstValid data.VotingData.VoteLastValid = oad.VotingData.VoteLastValid data.VotingData.VoteKeyDilution = oad.VotingData.VoteKeyDilution + data.IncentiveEligible = oad.IncentiveEligible return } diff --git a/ledger/acctonline_expired_test.go b/ledger/acctonline_expired_test.go index ac7986a78b..e51494d184 100644 --- a/ledger/acctonline_expired_test.go +++ b/ledger/acctonline_expired_test.go @@ -244,9 +244,8 @@ type doubleLedgerAcctModel struct { } func newDoubleLedgerAcctModel(t testing.TB, proto protocol.ConsensusVersion, inMem bool) *doubleLedgerAcctModel { - // set 1 Algo for rewards pool size -- rewards math not supported by newMapOnlineAcctModel - genesisOpt := ledgertesting.TestGenesisRewardsPoolSize(basics.MicroAlgos{Raw: 1_000_000}) - genBalances, genAddrs, genSecrets := ledgertesting.NewTestGenesis(genesisOpt) + // rewards math not supported by newMapOnlineAcctModel + genBalances, genAddrs, genSecrets := ledgertesting.NewTestGenesis(ledgertesting.TurnOffRewards) cfg := config.GetDefaultLocal() opts := []simpleLedgerOption{simpleLedgerNotArchival()} if !inMem { diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 1cf080d15e..34ffb2f32a 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -385,11 +385,15 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base // TODO: make lookupOnlineAccountData returning extended version of ledgercore.VotingData ? od, err := ao.lookupOnlineAccountData(rnd, addr) require.NoError(t, err) - require.Equal(t, od.VoteID, data.VoteID) - require.Equal(t, od.SelectionID, data.SelectionID) - require.Equal(t, od.VoteFirstValid, data.VoteFirstValid) - require.Equal(t, od.VoteLastValid, data.VoteLastValid) - require.Equal(t, od.VoteKeyDilution, data.VoteKeyDilution) + + // If lookupOnlineAccountData returned something, it should agree with `data`. + if !od.VoteID.IsEmpty() { + require.Equal(t, od.VoteID, data.VoteID) + require.Equal(t, od.SelectionID, data.SelectionID) + require.Equal(t, od.VoteFirstValid, data.VoteFirstValid) + require.Equal(t, od.VoteLastValid, data.VoteLastValid) + require.Equal(t, od.VoteKeyDilution, data.VoteKeyDilution) + } rewardsDelta := rewards[rnd] - d.RewardsBase switch d.Status { @@ -504,6 +508,11 @@ func checkOnlineAcctUpdatesConsistency(t *testing.T, ao *onlineAccounts, rnd bas for i := 0; i < latest.Len(); i++ { addr, acct := latest.GetByIdx(i) od, err := ao.lookupOnlineAccountData(rnd, addr) + if od.VoteID.IsEmpty() { + // suspended accounts will be in `latest` (from ao.deltas), but + // `lookupOnlineAccountData` will return {}. + continue + } require.NoError(t, err) require.Equal(t, acct.VoteID, od.VoteID) require.Equal(t, acct.SelectionID, od.SelectionID) diff --git a/ledger/apply/asset_test.go b/ledger/apply/asset_test.go index eddc15757e..0280ecdb60 100644 --- a/ledger/apply/asset_test.go +++ b/ledger/apply/asset_test.go @@ -90,7 +90,7 @@ func TestAssetTransfer(t *testing.T) { } var ad transactions.ApplyData - err := AssetTransfer(tx.AssetTransferTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, &ad) + err := AssetTransfer(tx.AssetTransferTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, &ad) require.NoError(t, err) if config.Consensus[protocol.ConsensusCurrentVersion].EnableAssetCloseAmount { diff --git a/ledger/apply/keyreg.go b/ledger/apply/keyreg.go index 4fe0f0a326..f5326f8240 100644 --- a/ledger/apply/keyreg.go +++ b/ledger/apply/keyreg.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" ) @@ -31,7 +30,7 @@ var errKeyregGoingOnlineFirstVotingInFuture = errors.New("transaction tries to m // Keyreg applies a KeyRegistration transaction using the Balances interface. func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, balances Balances, spec transactions.SpecialAddresses, ad *transactions.ApplyData, round basics.Round) error { if header.Sender == spec.FeeSink { - return fmt.Errorf("cannot register participation key for fee sink's address %v ", header.Sender) + return fmt.Errorf("cannot register participation key for fee sink's address %v", header.Sender) } // Get the user's balance entry @@ -54,7 +53,7 @@ func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, bal if params.EnableStateProofKeyregCheck { record.StateProofID = keyreg.StateProofPK } - if (keyreg.VotePK == crypto.OneTimeSignatureVerifier{} || keyreg.SelectionPK == crypto.VRFVerifier{}) { + if keyreg.VotePK.IsEmpty() || keyreg.SelectionPK.IsEmpty() { if keyreg.Nonparticipation { if params.SupportBecomeNonParticipatingTransactions { record.Status = basics.NotParticipating @@ -67,6 +66,8 @@ func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, bal record.VoteFirstValid = 0 record.VoteLastValid = 0 record.VoteKeyDilution = 0 + // IncentiveEligible is not reset, because the account has gracefully + // gone offline. They should be able to get back online without paying again. } else { if params.EnableKeyregCoherencyCheck { if keyreg.VoteLast <= round { @@ -77,9 +78,15 @@ func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, bal } } record.Status = basics.Online + if params.Payouts.Enabled { + record.LastHeartbeat = header.FirstValid + } record.VoteFirstValid = keyreg.VoteFirst record.VoteLastValid = keyreg.VoteLast record.VoteKeyDilution = keyreg.VoteKeyDilution + if header.Fee.Raw >= params.Payouts.GoOnlineFee && params.Payouts.Enabled { + record.IncentiveEligible = true + } } // Write the updated entry diff --git a/ledger/apply/keyreg_test.go b/ledger/apply/keyreg_test.go index e64beea4b4..41976257ea 100644 --- a/ledger/apply/keyreg_test.go +++ b/ledger/apply/keyreg_test.go @@ -136,20 +136,20 @@ func TestKeyregApply(t *testing.T) { // Going from offline to online should be okay mockBal.addrs[src] = basics.AccountData{Status: basics.Offline} - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) require.NoError(t, err) // Going from online to nonparticipatory should be okay, if the protocol supports that if mockBal.ConsensusParams().SupportBecomeNonParticipatingTransactions { tx.KeyregTxnFields = transactions.KeyregTxnFields{} tx.KeyregTxnFields.Nonparticipation = true - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) require.NoError(t, err) // Nonparticipatory accounts should not be able to change status mockBal.addrs[src] = basics.AccountData{Status: basics.NotParticipating} - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) - require.Error(t, err) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) + require.ErrorContains(t, err, "cannot change online/offline status of non-participating account") } mockBal.version = "future" @@ -171,25 +171,25 @@ func TestKeyregApply(t *testing.T) { }, } mockBal.addrs[src] = basics.AccountData{Status: basics.Offline} - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(999)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(999)) require.NoError(t, err) - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(1000)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(1000)) require.Equal(t, errKeyregGoingOnlineExpiredParticipationKey, err) - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(1001)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(1001)) require.Equal(t, errKeyregGoingOnlineExpiredParticipationKey, err) tx.KeyregTxnFields.VoteFirst = basics.Round(1100) tx.KeyregTxnFields.VoteLast = basics.Round(1200) - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(1098)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(1098)) require.Equal(t, errKeyregGoingOnlineFirstVotingInFuture, err) - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(1099)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(1099)) require.NoError(t, err) - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(1100)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(1100)) require.NoError(t, err) testStateProofPKBeingStored(t, tx, mockBal) @@ -197,7 +197,7 @@ func TestKeyregApply(t *testing.T) { } func testStateProofPKBeingStored(t *testing.T, tx transactions.Transaction, mockBal *keyregTestBalances) { - err := Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(1100)) + err := Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(1100)) require.NoError(t, err) // expects no error with empty keyRegistration attempt rec, err := mockBal.Get(tx.Header.Sender, false) @@ -215,7 +215,7 @@ func TestStateProofPKKeyReg(t *testing.T) { tx := createTestTxn(t, src, secretParticipation, vrfSecrets) mockBal := makeMockBalances(protocol.ConsensusV30) - err := Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) + err := Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) require.NoError(t, err) acct, err := mockBal.Get(tx.Src(), false) @@ -223,7 +223,7 @@ func TestStateProofPKKeyReg(t *testing.T) { require.True(t, acct.StateProofID.IsEmpty()) mockBal = makeMockBalances(protocol.ConsensusCurrentVersion) - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) require.NoError(t, err) acct, err = mockBal.Get(tx.Src(), false) @@ -232,7 +232,7 @@ func TestStateProofPKKeyReg(t *testing.T) { // go offline in current consensus version: StateProofID should be empty emptyKeyreg := transactions.KeyregTxnFields{} - err = Keyreg(emptyKeyreg, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) + err = Keyreg(emptyKeyreg, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) require.NoError(t, err) acct, err = mockBal.Get(tx.Src(), false) @@ -241,7 +241,7 @@ func TestStateProofPKKeyReg(t *testing.T) { // run same test using vFuture mockBal = makeMockBalances(protocol.ConsensusFuture) - err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) + err = Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) require.NoError(t, err) acct, err = mockBal.Get(tx.Src(), false) @@ -249,7 +249,7 @@ func TestStateProofPKKeyReg(t *testing.T) { require.False(t, acct.StateProofID.IsEmpty()) // go offline in vFuture: StateProofID should be empty - err = Keyreg(emptyKeyreg, tx.Header, mockBal, transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0)) + err = Keyreg(emptyKeyreg, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0)) require.NoError(t, err) acct, err = mockBal.Get(tx.Src(), false) diff --git a/ledger/apply/payment.go b/ledger/apply/payment.go index 8483bae92b..908e8eb926 100644 --- a/ledger/apply/payment.go +++ b/ledger/apply/payment.go @@ -19,28 +19,10 @@ package apply import ( "fmt" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" ) -func checkSpender(payment transactions.PaymentTxnFields, header transactions.Header, spec transactions.SpecialAddresses, proto config.ConsensusParams) error { - if header.Sender == payment.CloseRemainderTo { - return fmt.Errorf("transaction cannot close account to its sender %v", header.Sender) - } - - // the FeeSink account may only spend to the IncentivePool - if header.Sender == spec.FeeSink { - if payment.Receiver != spec.RewardsPool { - return fmt.Errorf("cannot spend from fee sink's address %v to non incentive pool address %v", header.Sender, payment.Receiver) - } - if payment.CloseRemainderTo != (basics.Address{}) { - return fmt.Errorf("cannot close fee sink %v to %v", header.Sender, payment.CloseRemainderTo) - } - } - return nil -} - // Payment changes the balances according to this transaction. // The ApplyData argument should reflect the changes made by // apply(). It may already include changes made by the caller diff --git a/ledger/apply/payment_test.go b/ledger/apply/payment_test.go index d377211a17..6e756350fb 100644 --- a/ledger/apply/payment_test.go +++ b/ledger/apply/payment_test.go @@ -104,15 +104,16 @@ func TestPaymentApply(t *testing.T) { }, } var ad transactions.ApplyData - err := Payment(tx.PaymentTxnFields, tx.Header, mockBalV0, transactions.SpecialAddresses{FeeSink: feeSink}, &ad) + err := Payment(tx.PaymentTxnFields, tx.Header, mockBalV0, transactions.SpecialAddresses{}, &ad) require.NoError(t, err) } func TestCheckSpender(t *testing.T) { partitiontest.PartitionTest(t) - mockBalV0 := makeMockBalances(protocol.ConsensusCurrentVersion) - mockBalV7 := makeMockBalances(protocol.ConsensusV7) + v7 := config.Consensus[protocol.ConsensusV7] + v39 := config.Consensus[protocol.ConsensusV39] + vFuture := config.Consensus[protocol.ConsensusFuture] secretSrc := keypair() src := basics.Address(secretSrc.SignatureVerifier) @@ -134,19 +135,33 @@ func TestCheckSpender(t *testing.T) { }, } - tx.Sender = basics.Address(feeSink) - require.Error(t, checkSpender(tx.PaymentTxnFields, tx.Header, spec, mockBalV0.ConsensusParams())) + tx.Sender = feeSink + require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7), + "to non incentive pool address") + require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39), + "to non incentive pool address") + require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture), + "cannot spend from fee sink") - poolAddr := basics.Address(poolAddr) tx.Receiver = poolAddr - require.NoError(t, checkSpender(tx.PaymentTxnFields, tx.Header, spec, mockBalV0.ConsensusParams())) + require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7)) + require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39)) + require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture), + "cannot spend from fee sink") tx.CloseRemainderTo = poolAddr - require.Error(t, checkSpender(tx.PaymentTxnFields, tx.Header, spec, mockBalV0.ConsensusParams())) - require.Error(t, checkSpender(tx.PaymentTxnFields, tx.Header, spec, mockBalV7.ConsensusParams())) - + require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7), + "cannot close fee sink") + require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39), + "cannot close fee sink") + require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture), + "cannot spend from fee sink") + + // When not sending from fee sink, everything's fine tx.Sender = src - require.NoError(t, checkSpender(tx.PaymentTxnFields, tx.Header, spec, mockBalV7.ConsensusParams())) + require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7)) + require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39)) + require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture)) } func TestPaymentValidation(t *testing.T) { diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 167d22f2dc..8e40d327e9 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -22,10 +22,12 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "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/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -35,7 +37,7 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -// TestPayAction ensures a pay in teal affects balances +// TestPayAction ensures a inner pay transaction affects balances func TestPayAction(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -58,6 +60,23 @@ func TestPayAction(t *testing.T) { itxn_submit `)) + // We're going to test some payout effects here too, so that we have an inner transaction example. + proposer := basics.Address{0x01, 0x02, 0x03} + dl.txns(&txntest.Txn{ + Type: "pay", + Sender: addrs[7], + Receiver: proposer, + Amount: 1_000_000 * 1_000_000, // 1 million algos is surely an eligible amount + }, &txntest.Txn{ + Type: "keyreg", + Sender: proposer, + Fee: 3_000_000, + VotePK: crypto.OneTimeSignatureVerifier{0x01}, + SelectionPK: crypto.VRFVerifier{0x02}, + StateProofPK: merklesignature.Commitment{0x03}, + VoteFirst: 1, VoteLast: 1000, + }) + payout1 := txntest.Txn{ Type: "appl", Sender: addrs[1], @@ -65,7 +84,32 @@ func TestPayAction(t *testing.T) { Accounts: []basics.Address{addrs[1]}, // pay self } - dl.fullBlock(&payout1) + presink := micros(dl.t, dl.generator, genBalances.FeeSink) + preprop := micros(dl.t, dl.generator, proposer) + dl.t.Log("presink", presink, "preprop", preprop) + dl.beginBlock() + dl.txns(&payout1) + vb := dl.endBlock(proposer) + const payoutsVer = 40 + if ver >= payoutsVer { + require.True(t, dl.generator.GenesisProto().Payouts.Enabled) + require.EqualValues(t, 2000, vb.Block().FeesCollected.Raw) + } else { + require.False(t, dl.generator.GenesisProto().Payouts.Enabled) + require.Zero(t, vb.Block().FeesCollected) + } + + postsink := micros(dl.t, dl.generator, genBalances.FeeSink) + postprop := micros(dl.t, dl.generator, proposer) + + dl.t.Log("postsink", postsink, "postprop", postprop) + if ver >= payoutsVer { + bonus := 10_000_000 // config/consensus.go + assert.EqualValues(t, bonus-500, presink-postsink) // based on 75% in config/consensus.go + require.EqualValues(t, bonus+1500, postprop-preprop) + } else { + require.EqualValues(t, 2000, postsink-presink) // no payouts yet + } ad0 := micros(dl.t, dl.generator, addrs[0]) ad1 := micros(dl.t, dl.generator, addrs[1]) @@ -90,7 +134,7 @@ func TestPayAction(t *testing.T) { ApplicationID: ai, Accounts: []basics.Address{addrs[2]}, // pay other } - vb := dl.fullBlock(&payout2) + vb = dl.fullBlock(&payout2) // confirm that modifiedAccounts can see account in inner txn deltas := vb.Delta() @@ -3344,7 +3388,8 @@ ok: vb := dl.endBlock() deltas := vb.Delta() - params, _ := deltas.Accts.GetAppParams(addrs[0], appID) + params, ok := deltas.Accts.GetAppParams(addrs[0], appID) + require.True(t, ok) require.Equal(t, basics.TealKeyValue{ "caller": {Type: basics.TealBytesType, Bytes: string(addrs[0][:])}, "creator": {Type: basics.TealBytesType, Bytes: string(addrs[0][:])}, diff --git a/ledger/double_test.go b/ledger/double_test.go index c08212e373..0854943636 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -19,6 +19,7 @@ package ledger import ( "testing" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" @@ -48,6 +49,9 @@ type DoubleLedger struct { validator *Ledger eval *eval.BlockEvaluator + + // proposer is the default proposer unless one is supplied to endBlock. + proposer basics.Address } func (dl DoubleLedger) Close() { @@ -59,7 +63,8 @@ func (dl DoubleLedger) Close() { func NewDoubleLedger(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local, opts ...simpleLedgerOption) DoubleLedger { g := newSimpleLedgerWithConsensusVersion(t, balances, cv, cfg, opts...) v := newSimpleLedgerFull(t, balances, cv, g.GenesisHash(), cfg, opts...) - return DoubleLedger{t, g, v, nil} + // FeeSink as proposer will make old tests work as expected, because payouts will stay put. + return DoubleLedger{t, g, v, nil, balances.FeeSink} } func (dl *DoubleLedger) beginBlock() *eval.BlockEvaluator { @@ -134,8 +139,13 @@ func (dl *DoubleLedger) fullBlock(txs ...*txntest.Txn) *ledgercore.ValidatedBloc return dl.endBlock() } -func (dl *DoubleLedger) endBlock() *ledgercore.ValidatedBlock { - vb := endBlock(dl.t, dl.generator, dl.eval) +func (dl *DoubleLedger) endBlock(proposer ...basics.Address) *ledgercore.ValidatedBlock { + prp := dl.proposer + if len(proposer) > 0 { + require.Len(dl.t, proposer, 1, "endBlock() cannot specify multiple proposers") + prp = proposer[0] + } + vb := endBlock(dl.t, dl.generator, dl.eval, prp) if dl.validator != nil { // Allows setting to nil while debugging, to simplify checkBlock(dl.t, dl.validator, vb) } @@ -168,49 +178,34 @@ func (dl *DoubleLedger) reloadLedgers() { require.NoError(dl.t, dl.validator.reloadLedger()) } -func checkBlock(t testing.TB, checkLedger *Ledger, vb *ledgercore.ValidatedBlock) { - bl := vb.Block() +func checkBlock(t testing.TB, checkLedger *Ledger, gvb *ledgercore.ValidatedBlock) { + bl := gvb.Block() msg := bl.MarshalMsg(nil) var reconstituted bookkeeping.Block _, err := reconstituted.UnmarshalMsg(msg) require.NoError(t, err) - check := nextCheckBlock(t, checkLedger, reconstituted.RewardsState) - var group []transactions.SignedTxnWithAD - for _, stib := range reconstituted.Payset { - stxn, ad, err := reconstituted.BlockHeader.DecodeSignedTxn(stib) - require.NoError(t, err) - stad := transactions.SignedTxnWithAD{SignedTxn: stxn, ApplyData: ad} - // If txn we're looking at belongs in the current group, append - if group == nil || (!stxn.Txn.Group.IsZero() && group[0].Txn.Group == stxn.Txn.Group) { - group = append(group, stad) - } else if group != nil { - err := check.TransactionGroup(group) - require.NoError(t, err) - group = []transactions.SignedTxnWithAD{stad} - } - } - if group != nil { - err := check.TransactionGroup(group) - require.NoError(t, err, "%+v", reconstituted.Payset) - } - check.SetGenerateForTesting(true) - cb := endBlock(t, checkLedger, check) - check.SetGenerateForTesting(false) - require.Equal(t, vb.Block(), cb.Block()) - - // vb.Delta() need not actually be Equal, in the sense of require.Equal - // because the order of the records in Accts is determined by the way the - // cb.sdeltas map (and then the maps in there) is iterated when the - // StateDelta is constructed by roundCowState.deltas(). They should be - // semantically equivalent, but those fields are not exported, so checking - // equivalence is hard. If vb.Delta() is, in fact, different, even though - // vb.Block() is the same, then there is something seriously broken going - // on, that is unlikely to have anything to do with these tests. So upshot: - // we skip trying a complicated equality check. - - // This is the part of checking Delta() equality that wouldn't work right. - // require.Equal(t, vb.Delta().Accts, cb.Delta().Accts) + cvb, err := validateWithoutSignatures(t, checkLedger, reconstituted) + require.NoError(t, err) + cvbd := cvb.Delta() + cvbd.Dehydrate() + gvbd := gvb.Delta() + gvbd.Dehydrate() + + // There are some things in the deltas that won't be identical. Erase them. + // Hdr was put in here at _start_ of block, and not updated. So gvb is in + // initial state, cvd got to see the whole thing. + gvbd.Hdr = nil + cvbd.Hdr = nil + + require.Equal(t, gvbd, cvbd) + + // Hydration/Dehydration is done in-place, so rehydrate so to avoid external evidence + cvbd.Hydrate() + gvbd.Hydrate() + + err = checkLedger.AddValidatedBlock(*cvb, agreement.Certificate{}) + require.NoError(t, err) } func nextCheckBlock(t testing.TB, ledger *Ledger, rs bookkeeping.RewardsState) *eval.BlockEvaluator { diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 8be898e05c..3aa7bd3905 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -290,8 +290,7 @@ func (cs *roundCowState) DelBox(appIdx basics.AppIndex, key string, appAddr basi func (cs *roundCowState) Perform(gi int, ep *logic.EvalParams) error { txn := &ep.TxnGroup[gi] - // move fee to pool - err := cs.Move(txn.Txn.Sender, ep.Specials.FeeSink, txn.Txn.Fee, &txn.ApplyData.SenderRewards, nil) + err := cs.takeFee(&txn.Txn, &txn.ApplyData.SenderRewards, ep) if err != nil { return err } diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 269ce570ff..23c415bbdf 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -95,6 +95,8 @@ type roundCowState struct { // prevTotals contains the accounts totals for the previous round. It's being used to calculate the totals for the new round // so that we could perform the validation test on these to ensure the block evaluator generate a valid changeset. prevTotals ledgercore.AccountTotals + + feesCollected basics.MicroAlgos } var childPool = sync.Pool{ @@ -299,6 +301,8 @@ func (cb *roundCowState) commitToParent() { cb.commitParent.mods.Txids[txid] = ledgercore.IncludedTransactions{LastValid: incTxn.LastValid, Intra: commitParentBaseIdx + incTxn.Intra} } cb.commitParent.txnCount += cb.txnCount + // no overflow because max supply is uint64, can't exceed that in fees paid + cb.commitParent.feesCollected, _ = basics.OAddA(cb.commitParent.feesCollected, cb.feesCollected) for txl, expires := range cb.mods.Txleases { cb.commitParent.mods.AddTxLease(txl, expires) @@ -342,6 +346,7 @@ func (cb *roundCowState) reset() { cb.compatibilityMode = false maps.Clear(cb.compatibilityGetKeyCache) cb.prevTotals = ledgercore.AccountTotals{} + cb.feesCollected = basics.MicroAlgos{} } // recycle resets the roundcowstate and returns it to the sync.Pool diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index 06d600d58a..81224a8d0b 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -282,6 +282,7 @@ func TestCowChildReflect(t *testing.T) { "compatibilityMode": {}, "compatibilityGetKeyCache": {}, "prevTotals": {}, + "feesCollected": {}, } cow := roundCowState{} @@ -289,7 +290,7 @@ func TestCowChildReflect(t *testing.T) { st := v.Type() for i := 0; i < v.NumField(); i++ { reflectedCowName := st.Field(i).Name - require.Containsf(t, cowFieldNames, reflectedCowName, "new field:\"%v\" added to roundCowState, please update roundCowState.reset() to handle it before fixing the test", reflectedCowName) + require.Containsf(t, cowFieldNames, reflectedCowName, "new field:\"%v\" added to roundCowState, please update roundCowState.reset() to handle it before fixing this test", reflectedCowName) } } diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index de1304988a..6599dd5918 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -20,12 +20,15 @@ import ( "context" "errors" "fmt" + "math" + "math/bits" "sync" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" @@ -716,19 +719,19 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts poolAddr := eval.prevHeader.RewardsPool // get the reward pool account data without any rewards - incentivePoolData, _, err := l.LookupWithoutRewards(eval.prevHeader.Round, poolAddr) + rewardsPoolData, _, err := l.LookupWithoutRewards(eval.prevHeader.Round, poolAddr) if err != nil { return nil, err } // this is expected to be a no-op, but update the rewards on the rewards pool if it was configured to receive rewards ( unlike mainnet ). - incentivePoolData = incentivePoolData.WithUpdatedRewards(prevProto, eval.prevHeader.RewardsLevel) + rewardsPoolData = rewardsPoolData.WithUpdatedRewards(prevProto, eval.prevHeader.RewardsLevel) if evalOpts.Generate { if eval.proto.SupportGenesisHash { eval.block.BlockHeader.GenesisHash = eval.genesisHash } - eval.block.BlockHeader.RewardsState = eval.prevHeader.NextRewardsState(hdr.Round, proto, incentivePoolData.MicroAlgos, prevTotals.RewardUnits(), logging.Base()) + eval.block.BlockHeader.RewardsState = eval.prevHeader.NextRewardsState(hdr.Round, proto, rewardsPoolData.MicroAlgos, prevTotals.RewardUnits(), logging.Base()) } // set the eval state with the current header eval.state = makeRoundCowState(base, eval.block.BlockHeader, proto, eval.prevHeader.TimeStamp, prevTotals, evalOpts.PaysetHint) @@ -740,7 +743,7 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts } // Check that the rewards rate, level and residue match expected values - expectedRewardsState := eval.prevHeader.NextRewardsState(hdr.Round, proto, incentivePoolData.MicroAlgos, prevTotals.RewardUnits(), logging.Base()) + expectedRewardsState := eval.prevHeader.NextRewardsState(hdr.Round, proto, rewardsPoolData.MicroAlgos, prevTotals.RewardUnits(), logging.Base()) if eval.block.RewardsState != expectedRewardsState { return nil, fmt.Errorf("bad rewards state: %+v != %+v", eval.block.RewardsState, expectedRewardsState) } @@ -751,7 +754,7 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts } } - // Withdraw rewards from the incentive pool + // Withdraw rewards from the pool var ot basics.OverflowTracker rewardsPerUnit := ot.Sub(eval.block.BlockHeader.RewardsLevel, eval.prevHeader.RewardsLevel) if ot.Overflowed { @@ -1165,12 +1168,27 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * return nil } +func (cs *roundCowState) takeFee(tx *transactions.Transaction, senderRewards *basics.MicroAlgos, ep *logic.EvalParams) error { + err := cs.Move(tx.Sender, ep.Specials.FeeSink, tx.Fee, senderRewards, nil) + if err != nil { + return err + } + // transactions from FeeSink should be exceedingly rare. But we can't count + // them in feesCollected because there are no net algos added to the Sink + if tx.Sender == ep.Specials.FeeSink { + return nil + } + // overflow impossible, since these sum the fees actually paid and max supply is uint64 + cs.feesCollected, _ = basics.OAddA(cs.feesCollected, tx.Fee) + return nil + +} + // applyTransaction changes the balances according to this transaction. func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, cow *roundCowState, evalParams *logic.EvalParams, gi int, ctr uint64) (ad transactions.ApplyData, err error) { params := cow.ConsensusParams() - // move fee to pool - err = cow.Move(tx.Sender, eval.specials.FeeSink, tx.Fee, &ad.SenderRewards, nil) + err = cow.takeFee(&tx, &ad.SenderRewards, evalParams) if err != nil { return } @@ -1272,7 +1290,18 @@ func (eval *BlockEvaluator) endOfBlock() error { eval.block.TxnCounter = 0 } - eval.generateExpiredOnlineAccountsList() + if eval.proto.Payouts.Enabled { + // Determine how much the proposer should be paid. Agreement code + // can cancel this payment by zero'ing the ProposerPayout if the + // proposer is found to be ineligible. See WithProposer(). + eval.block.FeesCollected = eval.state.feesCollected + eval.block.BlockHeader.ProposerPayout, err = eval.proposerPayout() + if err != nil { + return err + } + } + + eval.generateKnockOfflineAccountsList() if eval.proto.StateProofInterval > 0 { var basicStateProof bookkeeping.StateProofTrackingData @@ -1288,13 +1317,17 @@ func (eval *BlockEvaluator) endOfBlock() error { } } - err := eval.validateExpiredOnlineAccounts() - if err != nil { + if err := eval.validateExpiredOnlineAccounts(); err != nil { + return err + } + if err := eval.resetExpiredOnlineAccountsParticipationKeys(); err != nil { return err } - err = eval.resetExpiredOnlineAccountsParticipationKeys() - if err != nil { + if err := eval.validateAbsentOnlineAccounts(); err != nil { + return err + } + if err := eval.suspendAbsentAccounts(); err != nil { return err } @@ -1316,6 +1349,10 @@ func (eval *BlockEvaluator) endOfBlock() error { return fmt.Errorf("txn count wrong: %d != %d", eval.block.TxnCounter, expectedTxnCount) } + if err := eval.validateForPayouts(); err != nil { + return err + } + expectedVoters, expectedVotersWeight, err2 := eval.stateProofVotersAndTotal() if err2 != nil { return err2 @@ -1358,8 +1395,15 @@ func (eval *BlockEvaluator) endOfBlock() error { } } - err = eval.state.CalculateTotals() - if err != nil { + if err := eval.performPayout(); err != nil { + return err + } + + if err := eval.recordProposal(); err != nil { + return err + } + + if err := eval.state.CalculateTotals(); err != nil { return err } @@ -1370,41 +1414,265 @@ func (eval *BlockEvaluator) endOfBlock() error { return nil } -// generateExpiredOnlineAccountsList creates the list of the expired participation accounts by traversing over the -// modified accounts in the state deltas and testing if any of them needs to be reset. -func (eval *BlockEvaluator) generateExpiredOnlineAccountsList() { +func (eval *BlockEvaluator) validateForPayouts() error { + if !eval.proto.Payouts.Enabled { + if !eval.block.FeesCollected.IsZero() { + return fmt.Errorf("feesCollected %d present when payouts disabled", eval.block.FeesCollected.Raw) + } + if !eval.block.Proposer().IsZero() { + return fmt.Errorf("proposer %v present when payouts disabled", eval.block.Proposer()) + } + if !eval.block.ProposerPayout().IsZero() { + return fmt.Errorf("payout %d present when payouts disabled", eval.block.ProposerPayout().Raw) + } + return nil + } + + if eval.block.FeesCollected != eval.state.feesCollected { + return fmt.Errorf("fees collected wrong: %v != %v", eval.block.FeesCollected, eval.state.feesCollected) + } + + // agreement will check that the payout is zero if the proposer is + // ineligible, but we must check that it is correct if non-zero. We + // allow it to be too low. A proposer can be algruistic. + expectedPayout, err := eval.proposerPayout() + if err != nil { + return err + } + payout := eval.block.ProposerPayout() + if payout.Raw > expectedPayout.Raw { + return fmt.Errorf("proposal wants %d payout, %d is allowed", payout.Raw, expectedPayout.Raw) + } + + // agreement will check that the proposer is correct (we can't because + // we don't see the bundle), but agreement allows the proposer to be set + // even if Payouts is not enabled (and unset any time). So make sure + // it's set only if it should be. + if !eval.generate { // if generating, proposer is set later by agreement + proposer := eval.block.Proposer() + if proposer.IsZero() { + return fmt.Errorf("proposer missing when payouts enabled") + } + // a closed account cannot get payout + if !payout.IsZero() { + prp, err := eval.state.Get(proposer, false) + if err != nil { + return err + } + if prp.IsZero() { + return fmt.Errorf("proposer %v is closed but expects payout %d", proposer, payout.Raw) + } + } + } + return nil +} + +func (eval *BlockEvaluator) performPayout() error { + proposer := eval.block.Proposer() + // The proposer won't be present yet when generating a block, nor before enabled + if proposer.IsZero() { + return nil + } + + payout := eval.block.ProposerPayout() + + if !payout.IsZero() { + err := eval.state.Move(eval.block.FeeSink, proposer, payout, nil, nil) + if err != nil { + return err + } + } + return nil +} + +func (eval *BlockEvaluator) recordProposal() error { + proposer := eval.block.Proposer() + // The proposer won't be present yet when generating a block, nor before enabled + if proposer.IsZero() { + return nil + } + + prp, err := eval.state.Get(proposer, false) + if err != nil { + return err + } + // Record the LastProposed round, except in the unlikely case that a + // proposer has closed their account, but is still voting (it takes + // 320 rounds to be effective). Recording would prevent GC. + if !prp.IsZero() { + prp.LastProposed = eval.Round() + } + // An account could propose, even while suspended, because of the + // 320 round lookback. Doing so is evidence the account is + // operational. Unsuspend. But the account will remain not + // IncentiveElgible until they keyreg again with the extra fee. + if prp.Suspended() { + prp.Status = basics.Online + } + err = eval.state.Put(proposer, prp) + if err != nil { + return err + } + return nil +} + +func (eval *BlockEvaluator) proposerPayout() (basics.MicroAlgos, error) { + incentive, _ := basics.NewPercent(eval.proto.Payouts.Percent).DivvyAlgos(eval.block.FeesCollected) + total, o := basics.OAddA(incentive, eval.block.Bonus) + if o { + return basics.MicroAlgos{}, fmt.Errorf("payout overflowed adding bonus incentive %d %d", incentive, eval.block.Bonus) + } + + sink, err := eval.state.lookup(eval.block.FeeSink) + if err != nil { + return basics.MicroAlgos{}, err + } + available := sink.AvailableBalance(&eval.proto) + return basics.MinA(total, available), nil +} + +type challenge struct { + // round is when the challenge occurred. 0 means this is not a challenge. + round basics.Round + // accounts that match the first `bits` of `seed` must propose or heartbeat to stay online + seed committee.Seed + bits int +} + +// generateKnockOfflineAccountsList creates the lists of expired or absent +// participation accounts by traversing over the modified accounts in the state +// deltas and testing if any of them needs to be reset/suspended. Expiration +// takes precedence - if an account is expired, it should be knocked offline and +// key material deleted. If it is only suspended, the key material will remain. +func (eval *BlockEvaluator) generateKnockOfflineAccountsList() { if !eval.generate { return } - // We are going to find the list of modified accounts and the - // current round that is being evaluated. - // Then we are going to go through each modified account and - // see if it meets the criteria for adding it to the expired - // participation accounts list. - modifiedAccounts := eval.state.modifiedAccounts() - currentRound := eval.Round() + current := eval.Round() - expectedMaxNumberOfExpiredAccounts := eval.proto.MaxProposedExpiredOnlineAccounts + maxExpirations := eval.proto.MaxProposedExpiredOnlineAccounts + maxSuspensions := eval.proto.Payouts.MaxMarkAbsent + + updates := &eval.block.ParticipationUpdates + + ch := activeChallenge(&eval.proto, uint64(eval.Round()), eval.state) - for i := 0; i < len(modifiedAccounts) && len(eval.block.ParticipationUpdates.ExpiredParticipationAccounts) < expectedMaxNumberOfExpiredAccounts; i++ { - accountAddr := modifiedAccounts[i] - acctDelta, found := eval.state.mods.Accts.GetData(accountAddr) + for _, accountAddr := range eval.state.modifiedAccounts() { + acctData, found := eval.state.mods.Accts.GetData(accountAddr) if !found { continue } - // true if the account is online - isOnline := acctDelta.Status == basics.Online - // true if the accounts last valid round has passed - pastCurrentRound := acctDelta.VoteLastValid < currentRound + // Regardless of being online or suspended, if voting data exists, the + // account can be expired to remove it. This means an offline account + // can be expired (because it was already suspended). + if !acctData.VoteID.IsEmpty() { + expiresBeforeCurrent := acctData.VoteLastValid < current + if expiresBeforeCurrent && + len(updates.ExpiredParticipationAccounts) < maxExpirations { + updates.ExpiredParticipationAccounts = append( + updates.ExpiredParticipationAccounts, + accountAddr, + ) + continue // if marking expired, do not also suspend + } + } - if isOnline && pastCurrentRound { - eval.block.ParticipationUpdates.ExpiredParticipationAccounts = append( - eval.block.ParticipationUpdates.ExpiredParticipationAccounts, - accountAddr, - ) + if len(updates.AbsentParticipationAccounts) >= maxSuspensions { + continue // no more room (don't break the loop, since we may have more expiries) } + + if acctData.Status == basics.Online { + lastSeen := max(acctData.LastProposed, acctData.LastHeartbeat) + if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgos, lastSeen, current) || + failsChallenge(ch, accountAddr, lastSeen) { + updates.AbsentParticipationAccounts = append( + updates.AbsentParticipationAccounts, + accountAddr, + ) + } + } + } +} + +// delete me in Go 1.21 +func max(a, b basics.Round) basics.Round { + if a > b { + return a } + return b +} + +// bitsMatch checks if the first n bits of two byte slices match. Written to +// work on arbitrary slices, but we expect that n is small. Only user today +// calls with n=5. +func bitsMatch(a, b []byte, n int) bool { + // Ensure n is a valid number of bits to compare + if n < 0 || n > len(a)*8 || n > len(b)*8 { + return false + } + + // Compare entire bytes when n is bigger than 8 + for i := 0; i < n/8; i++ { + if a[i] != b[i] { + return false + } + } + remaining := n % 8 + if remaining == 0 { + return true + } + return bits.LeadingZeros8(a[n/8]^b[n/8]) >= remaining +} + +func isAbsent(totalOnlineStake basics.MicroAlgos, acctStake basics.MicroAlgos, lastSeen basics.Round, current basics.Round) bool { + // Don't consider accounts that were online when payouts went into effect as + // absent. They get noticed the next time they propose or keyreg, which + // ought to be soon, if they are high stake or want to earn incentives. + if lastSeen == 0 { + return false + } + // See if the account has exceeded 10x their expected observation interval. + allowableLag, o := basics.Muldiv(10, totalOnlineStake.Raw, acctStake.Raw) + if o { + // This can't happen with 10B total possible stake, but if we imagine + // another algorand network with huge possible stake, this seems reasonable. + allowableLag = math.MaxInt64 / acctStake.Raw + } + return lastSeen+basics.Round(allowableLag) < current +} + +type headerSource interface { + BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) +} + +func activeChallenge(proto *config.ConsensusParams, current uint64, headers headerSource) challenge { + rules := proto.Payouts + // are challenges active? + if rules.ChallengeInterval == 0 || current < rules.ChallengeInterval { + return challenge{} + } + lastChallenge := current - (current % rules.ChallengeInterval) + // challenge is in effect if we're after one grace period, but before the 2nd ends. + if current <= lastChallenge+rules.ChallengeGracePeriod || + current > lastChallenge+2*rules.ChallengeGracePeriod { + return challenge{} + } + round := basics.Round(lastChallenge) + challengeHdr, err := headers.BlockHdr(round) + if err != nil { + panic(err) + } + challengeProto := config.Consensus[challengeHdr.CurrentProtocol] + // challenge is not considered if rules have changed since that round + if challengeProto.Payouts != rules { + return challenge{} + } + return challenge{round, challengeHdr.Seed, rules.ChallengeBits} +} + +func failsChallenge(ch challenge, address basics.Address, lastSeen basics.Round) bool { + return ch.round != 0 && bitsMatch(ch.seed[:], address[:], ch.bits) && lastSeen < ch.round } // validateExpiredOnlineAccounts tests the expired online accounts specified in ExpiredParticipationAccounts, and verify @@ -1427,7 +1695,7 @@ func (eval *BlockEvaluator) validateExpiredOnlineAccounts() error { // are unique. We make this map to keep track of previously seen address addressSet := make(map[basics.Address]bool, lengthOfExpiredParticipationAccounts) - // Validate that all expired accounts meet the current criteria + // Validate that all proposed accounts have expired keys currentRound := eval.Round() for _, accountAddr := range eval.block.ParticipationUpdates.ExpiredParticipationAccounts { @@ -1444,22 +1712,66 @@ func (eval *BlockEvaluator) validateExpiredOnlineAccounts() error { return fmt.Errorf("endOfBlock was unable to retrieve account %v : %w", accountAddr, err) } - // true if the account is online - isOnline := acctData.Status == basics.Online - // true if the accounts last valid round has passed - pastCurrentRound := acctData.VoteLastValid < currentRound - - if !isOnline { - return fmt.Errorf("endOfBlock found %v was not online but %v", accountAddr, acctData.Status) + if acctData.VoteID.IsEmpty() { + return fmt.Errorf("endOfBlock found expiration candidate %v had no vote key", accountAddr) } - if !pastCurrentRound { + if acctData.VoteLastValid >= currentRound { return fmt.Errorf("endOfBlock found %v round (%d) was not less than current round (%d)", accountAddr, acctData.VoteLastValid, currentRound) } } return nil } +// validateAbsentOnlineAccounts tests the accounts specified in +// AbsentParticipationAccounts, and verifies that they need to be suspended +func (eval *BlockEvaluator) validateAbsentOnlineAccounts() error { + if !eval.validate { + return nil + } + maxSuspensions := eval.proto.Payouts.MaxMarkAbsent + suspensionCount := len(eval.block.ParticipationUpdates.AbsentParticipationAccounts) + + // If the length of the array is strictly greater than our max then we have an error. + // This works when the expected number of accounts is zero (i.e. it is disabled) as well + if suspensionCount > maxSuspensions { + return fmt.Errorf("length of absent accounts (%d) was greater than expected (%d)", + suspensionCount, maxSuspensions) + } + + // For consistency with expired account handling, we preclude duplicates + addressSet := make(map[basics.Address]bool, suspensionCount) + + ch := activeChallenge(&eval.proto, uint64(eval.Round()), eval.state) + + for _, accountAddr := range eval.block.ParticipationUpdates.AbsentParticipationAccounts { + if _, exists := addressSet[accountAddr]; exists { + return fmt.Errorf("duplicate address found: %v", accountAddr) + } + addressSet[accountAddr] = true + + acctData, err := eval.state.lookup(accountAddr) + if err != nil { + return fmt.Errorf("unable to retrieve proposed absent account %v : %w", accountAddr, err) + } + + if acctData.Status != basics.Online { + return fmt.Errorf("proposed absent account %v was %v, not Online", accountAddr, acctData.Status) + } + + lastSeen := max(acctData.LastProposed, acctData.LastHeartbeat) + if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgos, lastSeen, eval.Round()) { + continue // ok. it's "normal absent" + } + if failsChallenge(ch, accountAddr, lastSeen) { + continue // ok. it's "challenge absent" + } + return fmt.Errorf("proposed absent account %v is not absent in %d, %d", + accountAddr, acctData.LastProposed, acctData.LastHeartbeat) + } + return nil +} + // resetExpiredOnlineAccountsParticipationKeys after all transactions and rewards are processed, modify the accounts so that their status is offline func (eval *BlockEvaluator) resetExpiredOnlineAccountsParticipationKeys() error { expectedMaxNumberOfExpiredAccounts := eval.proto.MaxProposedExpiredOnlineAccounts @@ -1490,6 +1802,24 @@ func (eval *BlockEvaluator) resetExpiredOnlineAccountsParticipationKeys() error return nil } +// suspendAbsentAccounts suspends the proposed list of absent accounts. +func (eval *BlockEvaluator) suspendAbsentAccounts() error { + for _, addr := range eval.block.ParticipationUpdates.AbsentParticipationAccounts { + acct, err := eval.state.lookup(addr) + if err != nil { + return err + } + + acct.Suspend() + + err = eval.state.putAccount(addr, acct) + if err != nil { + return err + } + } + return nil +} + // GenerateBlock produces a complete block from the BlockEvaluator. This is // used during proposal to get an actual block that will be proposed, after // feeding in tentative transactions into this block evaluator. @@ -1497,7 +1827,7 @@ func (eval *BlockEvaluator) resetExpiredOnlineAccountsParticipationKeys() error // After a call to GenerateBlock, the BlockEvaluator can still be used to // accept transactions. However, to guard against reuse, subsequent calls // to GenerateBlock on the same BlockEvaluator will fail. -func (eval *BlockEvaluator) GenerateBlock() (*ledgercore.ValidatedBlock, error) { +func (eval *BlockEvaluator) GenerateBlock(addrs []basics.Address) (*ledgercore.UnfinishedBlock, error) { if !eval.generate { logging.Base().Panicf("GenerateBlock() called but generate is false") } @@ -1511,7 +1841,17 @@ func (eval *BlockEvaluator) GenerateBlock() (*ledgercore.ValidatedBlock, error) return nil, err } - vb := ledgercore.MakeValidatedBlock(eval.block, eval.state.deltas()) + // look up set of participation accounts passed to GenerateBlock (possible proposers) + finalAccounts := make(map[basics.Address]ledgercore.AccountData, len(addrs)) + for i := range addrs { + acct, err := eval.state.lookup(addrs[i]) + if err != nil { + return nil, err + } + finalAccounts[addrs[i]] = acct + } + + vb := ledgercore.MakeUnfinishedBlock(eval.block, eval.state.deltas(), finalAccounts) eval.blockGenerated = true proto, ok := config.Consensus[eval.block.BlockHeader.CurrentProtocol] if !ok { diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 7884a7f894..994bedd561 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -30,10 +30,10 @@ import ( "github.com/algorand/go-algorand/agreement" "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/basics" basics_testing "github.com/algorand/go-algorand/data/basics/testing" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" @@ -182,8 +182,7 @@ func TestEvalAppStateCountsWithTxnGroup(t *testing.T) { partitiontest.PartitionTest(t) _, _, err := testEvalAppGroup(t, basics.StateSchema{NumByteSlice: 1}) - require.Error(t, err) - require.Contains(t, err.Error(), "store bytes count 2 exceeds schema bytes count 1") + require.ErrorContains(t, err, "store bytes count 2 exceeds schema bytes count 1") } // TestEvalAppAllocStateWithTxnGroup ensures roundCowState.deltas and applyStorageDelta @@ -213,7 +212,7 @@ func TestTestTransactionGroup(t *testing.T) { eval.proto = config.Consensus[protocol.ConsensusCurrentVersion] txgroup = make([]transactions.SignedTxn, eval.proto.MaxTxGroupSize+1) err = eval.TestTransactionGroup(txgroup) - require.Error(t, err) // too many + require.ErrorContains(t, err, "group size") } // test BlockEvaluator.transactionGroup() @@ -229,7 +228,7 @@ func TestPrivateTransactionGroup(t *testing.T) { eval.proto = config.Consensus[protocol.ConsensusCurrentVersion] txgroup = make([]transactions.SignedTxnWithAD, eval.proto.MaxTxGroupSize+1) err = eval.TransactionGroup(txgroup) - require.Error(t, err) // too many + require.ErrorContains(t, err, "group size") } func TestTransactionGroupWithTracer(t *testing.T) { @@ -647,7 +646,7 @@ func testnetFixupExecution(t *testing.T, headerRound basics.Round, poolBonus uin // won't work before funding bank if poolBonus > 0 { _, err = eval.workaroundOverspentRewards(rewardPoolBalance, headerRound) - require.Error(t, err) + require.ErrorContains(t, err, "unable to move funds from testnet bank") } bankAddr, _ := basics.UnmarshalChecksumAddress("GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A") @@ -946,7 +945,7 @@ func (ledger *evalTestLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeade } func (ledger *evalTestLedger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { - return nil, errors.New("untested code path") + return nil, nil } // GetCreator is like GetCreatorForRound, but for the latest round and race-free @@ -986,11 +985,15 @@ func (ledger *evalTestLedger) nextBlock(t testing.TB) *BlockEvaluator { // endBlock completes the block being created, returns the ValidatedBlock for inspection func (ledger *evalTestLedger) endBlock(t testing.TB, eval *BlockEvaluator) *ledgercore.ValidatedBlock { - validatedBlock, err := eval.GenerateBlock() + unfinishedBlock, err := eval.GenerateBlock(nil) require.NoError(t, err) - err = ledger.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) + // fake agreement's setting of header fields so later validates work. + seed := committee.Seed{} + crypto.RandBytes(seed[:]) + validatedBlock := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock().WithProposer(seed, testPoolAddr, true), unfinishedBlock.UnfinishedDeltas()) + err = ledger.AddValidatedBlock(validatedBlock, agreement.Certificate{}) require.NoError(t, err) - return validatedBlock + return &validatedBlock } // lookup gets the current accountdata for an address @@ -1078,6 +1081,7 @@ func (l *testCowBaseLedger) GetCreatorForRound(_ basics.Round, cindex basics.Cre func TestCowBaseCreatorsCache(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() addresses := make([]basics.Address, 3) for i := 0; i < len(addresses); i++ { @@ -1121,6 +1125,7 @@ func TestCowBaseCreatorsCache(t *testing.T) { // TestEvalFunctionForExpiredAccounts tests that the eval function will correctly mark accounts as offline func TestEvalFunctionForExpiredAccounts(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genesisInitState, addrs, keys := ledgertesting.GenesisWithProto(10, protocol.ConsensusFuture) @@ -1142,6 +1147,7 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { tmp.Status = basics.Online crypto.RandBytes(tmp.StateProofID[:]) crypto.RandBytes(tmp.SelectionID[:]) + crypto.RandBytes(tmp.VoteID[:]) genesisInitState.Accounts[addr] = tmp } @@ -1165,10 +1171,16 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { blkEval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) require.NoError(t, err) - // Advance the evaluator a couple rounds... + // Advance the evaluator a couple rounds, watching for lack of expiration for i := uint64(0); i < uint64(targetRound); i++ { - l.endBlock(t, blkEval) + vb := l.endBlock(t, blkEval) blkEval = l.nextBlock(t) + for _, acct := range vb.Block().ExpiredParticipationAccounts { + if acct == recvAddr { + // won't happen, because recvAddr didn't appear in block + require.Fail(t, "premature expiration") + } + } } require.Greater(t, uint64(blkEval.Round()), uint64(recvAddrLastValidRound)) @@ -1196,62 +1208,62 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { // Make sure we validate our block as well blkEval.validate = true - validatedBlock, err := blkEval.GenerateBlock() + unfinishedBlock, err := blkEval.GenerateBlock(nil) require.NoError(t, err) + // fake agreement's setting of header fields so later validates work + validatedBlock := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock().WithProposer(committee.Seed{}, testPoolAddr, true), unfinishedBlock.UnfinishedDeltas()) + + expired := false + for _, acct := range validatedBlock.Block().ExpiredParticipationAccounts { + if acct == recvAddr { + expired = true + } + } + require.True(t, expired) + _, err = Eval(context.Background(), l, validatedBlock.Block(), false, nil, nil, l.tracer) require.NoError(t, err) acctData, _ := blkEval.state.lookup(recvAddr) - require.Equal(t, merklesignature.Verifier{}.Commitment, acctData.StateProofID) - require.Equal(t, crypto.VRFVerifier{}, acctData.SelectionID) - - badBlock := *validatedBlock + require.Zero(t, acctData.StateProofID) + require.Zero(t, acctData.SelectionID) + require.Zero(t, acctData.VoteID) + goodBlock := validatedBlock.Block() - // First validate that bad block is fine if we dont touch it... - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) + // First validate that it's fine if we dont touch it. + _, err = Eval(context.Background(), l, goodBlock, true, verify.GetMockedCache(true), nil, l.tracer) require.NoError(t, err) - badBlock = *validatedBlock - // Introduce an unknown address to introduce an error - badBlockObj := badBlock.Block() - badBlockObj.ExpiredParticipationAccounts = append(badBlockObj.ExpiredParticipationAccounts, basics.Address{1}) - badBlock = ledgercore.MakeValidatedBlock(badBlockObj, badBlock.Delta()) - - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) - require.Error(t, err) + badBlock := goodBlock + badBlock.ExpiredParticipationAccounts = append(badBlock.ExpiredParticipationAccounts, basics.Address{1}) - badBlock = *validatedBlock - - addressToCopy := badBlock.Block().ExpiredParticipationAccounts[0] + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.ErrorContains(t, err, "expiration candidate") // Add more than the expected number of accounts - badBlockObj = badBlock.Block() + badBlock = goodBlock + addressToCopy := badBlock.ExpiredParticipationAccounts[0] for i := 0; i < blkEval.proto.MaxProposedExpiredOnlineAccounts+1; i++ { - badBlockObj.ExpiredParticipationAccounts = append(badBlockObj.ExpiredParticipationAccounts, addressToCopy) + badBlock.ExpiredParticipationAccounts = append(badBlock.ExpiredParticipationAccounts, addressToCopy) } - badBlock = ledgercore.MakeValidatedBlock(badBlockObj, badBlock.Delta()) - - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) - require.Error(t, err) - badBlock = *validatedBlock + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.ErrorContains(t, err, "length of expired accounts") // Duplicate an address - badBlockObj = badBlock.Block() - badBlockObj.ExpiredParticipationAccounts = append(badBlockObj.ExpiredParticipationAccounts, badBlockObj.ExpiredParticipationAccounts[0]) - badBlock = ledgercore.MakeValidatedBlock(badBlockObj, badBlock.Delta()) + badBlock = goodBlock + badBlock.ExpiredParticipationAccounts = append(badBlock.ExpiredParticipationAccounts, addressToCopy) - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) - require.Error(t, err) + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.ErrorContains(t, err, "duplicate address found") - badBlock = *validatedBlock + badBlock = goodBlock // sanity check that bad block is being actually copied and not just the pointer - _, err = Eval(context.Background(), l, badBlock.Block(), true, verify.GetMockedCache(true), nil, l.tracer) + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) require.NoError(t, err) - } type failRoundCowParent struct { @@ -1265,6 +1277,7 @@ func (p *failRoundCowParent) lookup(basics.Address) (ledgercore.AccountData, err // TestExpiredAccountGenerationWithDiskFailure tests edge cases where disk failures can lead to ledger look up failures func TestExpiredAccountGenerationWithDiskFailure(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genesisInitState, addrs, keys := ledgertesting.GenesisWithProto(10, protocol.ConsensusFuture) @@ -1338,22 +1351,179 @@ func TestExpiredAccountGenerationWithDiskFailure(t *testing.T) { eval.block.ExpiredParticipationAccounts = append(eval.block.ExpiredParticipationAccounts, recvAddr) err = eval.endOfBlock() - require.Error(t, err) + require.ErrorContains(t, err, "found expiration candidate") eval.block.ExpiredParticipationAccounts = []basics.Address{{}} eval.state.mods.Accts = ledgercore.AccountDeltas{} eval.state.lookupParent = &failRoundCowParent{} err = eval.endOfBlock() - require.Error(t, err) + require.ErrorContains(t, err, "disk I/O fail (on purpose)") err = eval.resetExpiredOnlineAccountsParticipationKeys() - require.Error(t, err) + require.ErrorContains(t, err, "disk I/O fail (on purpose)") +} + +// TestAbsenteeChecks tests that the eval function will correctly mark accounts as absent +func TestAbsenteeChecks(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genesisInitState, addrs, keys := ledgertesting.GenesisWithProto(10, protocol.ConsensusFuture) + + // add 32 more addresses, we can get a suspension by challenge + for i := 0; i < 32; i++ { + addrs = append(addrs, basics.Address{byte(i << 3), 0xaa}) + } + + // Set all addrs to online + for i, addr := range addrs { + tmp := genesisInitState.Accounts[addr] + tmp.Status = basics.Online + crypto.RandBytes(tmp.StateProofID[:]) + crypto.RandBytes(tmp.SelectionID[:]) + crypto.RandBytes(tmp.VoteID[:]) + tmp.VoteFirstValid = 1 + tmp.VoteLastValid = 1500 // large enough to avoid EXPIRATION, so we can see SUSPENSION + tmp.LastHeartbeat = 1 // non-zero allows suspensions + switch i { + case 1: + tmp.LastHeartbeat = 1150 // lie here so that addr[1] won't be suspended + case 2: + tmp.LastProposed = 1150 // lie here so that addr[2] won't be suspended + } + + genesisInitState.Accounts[addr] = tmp + } + + l := newTestLedger(t, bookkeeping.GenesisBalances{ + Balances: genesisInitState.Accounts, + FeeSink: testSinkAddr, + RewardsPool: testPoolAddr, + }) + + newBlock := bookkeeping.MakeBlock(l.blocks[0].BlockHeader) + + blkEval, err := l.StartEvaluator(newBlock.BlockHeader, 0, 0, nil) + require.NoError(t, err) + + // Advance the evaluator, watching for lack of suspensions since we don't + // suspend until a txn with a suspendable account appears + challenge := byte(0) + for i := uint64(0); i < uint64(1210); i++ { // A bit past one grace period (200) past challenge at 1000. + vb := l.endBlock(t, blkEval) + blkEval = l.nextBlock(t) + require.Zero(t, vb.Block().AbsentParticipationAccounts) + if vb.Block().Round() == 1000 { + challenge = vb.Block().BlockHeader.Seed[0] + } + } + challenged := basics.Address{(challenge >> 3) << 3, 0xaa} + + pay := func(i int, a basics.Address) transactions.Transaction { + return transactions.Transaction{ + Type: protocol.PaymentTx, + Header: transactions.Header{ + Sender: addrs[i], + Fee: minFee, + LastValid: blkEval.Round(), + GenesisHash: l.GenesisHash(), + }, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: a, + Amount: basics.MicroAlgos{Raw: 100_000}, + }, + } + } + + selfpay := func(i int) transactions.SignedTxn { + return pay(i, addrs[i]).Sign(keys[i]) + } + + require.NoError(t, blkEval.Transaction(selfpay(0), transactions.ApplyData{})) + require.NoError(t, blkEval.Transaction(selfpay(1), transactions.ApplyData{})) + require.NoError(t, blkEval.Transaction(selfpay(2), transactions.ApplyData{})) + for i := 0; i < 32; i++ { + require.NoError(t, blkEval.Transaction(pay(0, basics.Address{byte(i << 3), 0xaa}).Sign(keys[0]), + transactions.ApplyData{})) + } + + // Make sure we validate our block as well + blkEval.validate = true + + unfinishedBlock, err := blkEval.GenerateBlock(nil) + require.NoError(t, err) + // fake agreement's setting of header fields so later validates work + validatedBlock := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock().WithProposer(committee.Seed{}, testPoolAddr, true), unfinishedBlock.UnfinishedDeltas()) + + require.Zero(t, validatedBlock.Block().ExpiredParticipationAccounts) + require.Contains(t, validatedBlock.Block().AbsentParticipationAccounts, addrs[0], addrs[0].String()) + require.NotContains(t, validatedBlock.Block().AbsentParticipationAccounts, addrs[1], addrs[1].String()) + require.NotContains(t, validatedBlock.Block().AbsentParticipationAccounts, addrs[2], addrs[2].String()) + + // Of the 32 extra accounts, make sure only the one matching the challenge is suspended + require.Contains(t, validatedBlock.Block().AbsentParticipationAccounts, challenged, challenged.String()) + for i := byte(0); i < 32; i++ { + if i == challenge>>3 { + require.Equal(t, basics.Address{i << 3, 0xaa}, challenged) + continue + } + require.NotContains(t, validatedBlock.Block().AbsentParticipationAccounts, basics.Address{i << 3, 0xaa}) + } + + _, err = Eval(context.Background(), l, validatedBlock.Block(), false, nil, nil, l.tracer) + require.NoError(t, err) + + acctData, _ := blkEval.state.lookup(addrs[0]) + + require.NotZero(t, acctData.StateProofID) + require.NotZero(t, acctData.SelectionID) + require.NotZero(t, acctData.VoteID) + goodBlock := validatedBlock.Block() + + // First validate that it's fine if we dont touch it. + _, err = Eval(context.Background(), l, goodBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.NoError(t, err) + + // Introduce an address that shouldn't be suspended + badBlock := goodBlock + badBlock.AbsentParticipationAccounts = append(badBlock.AbsentParticipationAccounts, addrs[1]) + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.ErrorContains(t, err, "not absent") + + // An account that isn't even online + badBlock = goodBlock + badBlock.AbsentParticipationAccounts = append(badBlock.AbsentParticipationAccounts, basics.Address{0x01}) + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.ErrorContains(t, err, "not Online") + + // Add more than the expected number of accounts + badBlock = goodBlock + addressToCopy := badBlock.AbsentParticipationAccounts[0] + for i := 0; i < blkEval.proto.MaxProposedExpiredOnlineAccounts+1; i++ { + badBlock.AbsentParticipationAccounts = append(badBlock.AbsentParticipationAccounts, addressToCopy) + } + + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.ErrorContains(t, err, "length of absent accounts") + + // Duplicate an address + badBlock = goodBlock + badBlock.AbsentParticipationAccounts = append(badBlock.AbsentParticipationAccounts, addressToCopy) + + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.ErrorContains(t, err, "duplicate address found") + + badBlock = goodBlock + // sanity check that bad block is being actually copied and not just the pointer + _, err = Eval(context.Background(), l, badBlock, true, verify.GetMockedCache(true), nil, l.tracer) + require.NoError(t, err) } // TestExpiredAccountGeneration test that expired accounts are added to a block header and validated func TestExpiredAccountGeneration(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genesisInitState, addrs, keys := ledgertesting.GenesisWithProto(10, protocol.ConsensusFuture) @@ -1435,22 +1605,132 @@ func TestExpiredAccountGeneration(t *testing.T) { // Make sure we validate our block as well eval.validate = true - validatedBlock, err := eval.GenerateBlock() + unfinishedBlock, err := eval.GenerateBlock(nil) require.NoError(t, err) - listOfExpiredAccounts := validatedBlock.Block().ParticipationUpdates.ExpiredParticipationAccounts + listOfExpiredAccounts := unfinishedBlock.UnfinishedBlock().ParticipationUpdates.ExpiredParticipationAccounts - require.Equal(t, 1, len(listOfExpiredAccounts)) - expiredAccount := listOfExpiredAccounts[0] - require.Equal(t, expiredAccount, recvAddr) + require.Len(t, listOfExpiredAccounts, 1) + require.Equal(t, listOfExpiredAccounts[0], recvAddr) recvAcct, err := eval.state.lookup(recvAddr) require.NoError(t, err) require.Equal(t, basics.Offline, recvAcct.Status) - require.Equal(t, basics.Round(0), recvAcct.VoteFirstValid) - require.Equal(t, basics.Round(0), recvAcct.VoteLastValid) - require.Equal(t, uint64(0), recvAcct.VoteKeyDilution) - require.Equal(t, crypto.OneTimeSignatureVerifier{}, recvAcct.VoteID) - require.Equal(t, crypto.VRFVerifier{}, recvAcct.SelectionID) - require.Equal(t, merklesignature.Verifier{}.Commitment, recvAcct.StateProofID) + require.Zero(t, recvAcct.VoteFirstValid) + require.Zero(t, recvAcct.VoteLastValid) + require.Zero(t, recvAcct.VoteKeyDilution) + require.Zero(t, recvAcct.VoteID) + require.Zero(t, recvAcct.SelectionID) + require.Zero(t, recvAcct.StateProofID) +} + +func TestBitsMatch(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for b := 0; b <= 6; b++ { + require.True(t, bitsMatch([]byte{0x1}, []byte{0x2}, b), "%d", b) + } + require.False(t, bitsMatch([]byte{0x1}, []byte{0x2}, 7)) + require.False(t, bitsMatch([]byte{0x1}, []byte{0x2}, 8)) + require.False(t, bitsMatch([]byte{0x1}, []byte{0x2}, 9)) + + for b := 0; b <= 12; b++ { + require.True(t, bitsMatch([]byte{0x1, 0xff, 0xaa}, []byte{0x1, 0xf0}, b), "%d", b) + } + require.False(t, bitsMatch([]byte{0x1, 0xff, 0xaa}, []byte{0x1, 0xf0}, 13)) + + // on a byte boundary + require.True(t, bitsMatch([]byte{0x1}, []byte{0x1}, 8)) + require.False(t, bitsMatch([]byte{0x1}, []byte{0x1}, 9)) + require.True(t, bitsMatch([]byte{0x1, 0xff}, []byte{0x1, 0x00}, 8)) + require.False(t, bitsMatch([]byte{0x1, 0xff}, []byte{0x1, 00}, 9)) +} + +func TestIsAbsent(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := assert.New(t) + + var absent = func(total uint64, acct uint64, last uint64, current uint64) bool { + return isAbsent(basics.Algos(total), basics.Algos(acct), basics.Round(last), basics.Round(current)) + } + // 1% of stake, absent for 1000 rounds + a.False(absent(1000, 10, 5000, 6000)) + a.True(absent(1000, 10, 5000, 6001)) // longer + a.True(absent(1000, 11, 5000, 6001)) // more acct stake + a.False(absent(1000, 9, 5000, 6001)) // less acct stake + a.False(absent(1001, 10, 5000, 6001)) // more online stake + // not absent if never seen + a.False(absent(1000, 10, 0, 6000)) + a.False(absent(1000, 10, 0, 6001)) +} + +func TestFailsChallenge(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := assert.New(t) + + // a valid challenge, with 4 matching bits, and an old last seen + a.True(failsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xbf, 0x34}, 10)) + + // challenge isn't "on" + a.False(failsChallenge(challenge{round: 0, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xbf, 0x34}, 10)) + // node has appeared more recently + a.False(failsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xbf, 0x34}, 12)) + // bits don't match + a.False(failsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xcf, 0x34}, 10)) + // no enough bits match + a.False(failsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 5}, basics.Address{0xbf, 0x34}, 10)) +} + +type singleSource bookkeeping.BlockHeader + +func (ss singleSource) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { + return bookkeeping.BlockHeader(ss), nil +} + +func TestActiveChallenge(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + a := assert.New(t) + + nowHeader := bookkeeping.BlockHeader{ + UpgradeState: bookkeeping.UpgradeState{ + // Here the rules are on, so they certainly differ from rules in oldHeader's params + CurrentProtocol: protocol.ConsensusFuture, + }, + } + now := config.Consensus[nowHeader.CurrentProtocol] + + // simplest test. when interval=X and grace=G, X+G+1 is a challenge + inChallenge := now.Payouts.ChallengeInterval + now.Payouts.ChallengeGracePeriod + 1 + ch := activeChallenge(&now, inChallenge, singleSource(nowHeader)) + a.NotZero(ch.round) + + // all rounds before that have no challenge + for r := uint64(1); r < inChallenge; r++ { + ch := activeChallenge(&now, r, singleSource(nowHeader)) + a.Zero(ch.round, r) + } + + // ChallengeGracePeriod rounds allow challenges starting with inChallenge + for r := inChallenge; r < inChallenge+now.Payouts.ChallengeGracePeriod; r++ { + ch := activeChallenge(&now, r, singleSource(nowHeader)) + a.EqualValues(ch.round, now.Payouts.ChallengeInterval) + } + + // And the next round is again challenge-less + ch = activeChallenge(&now, inChallenge+now.Payouts.ChallengeGracePeriod, singleSource(nowHeader)) + a.Zero(ch.round) + + // ignore challenge if upgrade happened + oldHeader := bookkeeping.BlockHeader{ + UpgradeState: bookkeeping.UpgradeState{ + // We need a version from before payouts got turned on + CurrentProtocol: protocol.ConsensusV39, + }, + } + ch = activeChallenge(&now, inChallenge, singleSource(oldHeader)) + a.Zero(ch.round) } diff --git a/ledger/eval/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go index 57f4c5d92b..cb4b165c94 100644 --- a/ledger/eval/prefetcher/prefetcher_alignment_test.go +++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go @@ -261,6 +261,18 @@ type ledgerData struct { Creators map[creatable]struct{} } +// pretend adds the `before` addresses to the Accounts. It "pretends" that the +// addresses were prefetched, so we can get agreement with what was actually +// requested. We do this to include two addresses that are going to end up +// requested *before* prefetch is even attempted. So there's no point in +// PrefetchAccounts being modified to return them, they have been "prefetched" +// simply by accessing them. +func (ld *ledgerData) pretend(before ...basics.Address) { + for _, a := range before { + ld.Accounts[a] = struct{}{} + } +} + func prefetch(t *testing.T, l prefetcher.Ledger, txn transactions.Transaction) ledgerData { group := makeGroupFromTxn(txn) @@ -361,7 +373,7 @@ func TestEvaluatorPrefetcherAlignmentPayment(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -393,7 +405,7 @@ func TestEvaluatorPrefetcherAlignmentCreateAsset(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) // Only one (non-existing) asset is requested. Ignore it. require.Len(t, requested.Assets, 1) require.Len(t, requested.Assets[makeAddress(1)], 1) @@ -449,7 +461,7 @@ func TestEvaluatorPrefetcherAlignmentReconfigAsset(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -504,7 +516,7 @@ func TestEvaluatorPrefetcherAlignmentAssetOptIn(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -570,7 +582,7 @@ func TestEvaluatorPrefetcherAlignmentAssetOptInCloseTo(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -641,7 +653,7 @@ func TestEvaluatorPrefetcherAlignmentAssetTransfer(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) // zero transfer of any asset @@ -660,7 +672,7 @@ func TestEvaluatorPrefetcherAlignmentAssetTransfer(t *testing.T) { requested, prefetched = run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -741,7 +753,7 @@ func TestEvaluatorPrefetcherAlignmentAssetClawback(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -811,7 +823,7 @@ func TestEvaluatorPrefetcherAlignmentAssetFreeze(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -858,7 +870,7 @@ func TestEvaluatorPrefetcherAlignmentKeyreg(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -895,7 +907,7 @@ func TestEvaluatorPrefetcherAlignmentCreateApplication(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) // Only one (non-existing) asset is requested. Ignore it. require.Len(t, requested.Apps, 1) require.Len(t, requested.Apps[makeAddress(1)], 1) @@ -953,7 +965,7 @@ func TestEvaluatorPrefetcherAlignmentDeleteApplication(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -1011,7 +1023,7 @@ func TestEvaluatorPrefetcherAlignmentApplicationOptIn(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -1075,7 +1087,7 @@ func TestEvaluatorPrefetcherAlignmentApplicationCloseOut(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -1139,7 +1151,7 @@ func TestEvaluatorPrefetcherAlignmentApplicationClearState(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } @@ -1203,7 +1215,7 @@ func TestEvaluatorPrefetcherAlignmentApplicationCallAccountsDeclaration(t *testi requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) // Foreign accounts are not loaded, ensure they are not prefetched require.NotContains(t, prefetched.Accounts, makeAddress(5)) require.NotContains(t, prefetched.Accounts, makeAddress(3)) @@ -1271,7 +1283,7 @@ func TestEvaluatorPrefetcherAlignmentApplicationCallForeignAppsDeclaration(t *te requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) // Foreign apps are not loaded, ensure they are not prefetched require.NotContains(t, prefetched.Creators, creatable{cindex: 6, ctype: basics.AppCreatable}) require.NotContains(t, prefetched.Creators, creatable{cindex: 8, ctype: basics.AppCreatable}) @@ -1338,7 +1350,7 @@ func TestEvaluatorPrefetcherAlignmentApplicationCallForeignAssetsDeclaration(t * requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) // Foreign apps are not loaded, ensure they are not prefetched require.NotContains(t, prefetched.Creators, creatable{cindex: 6, ctype: basics.AssetCreatable}) require.NotContains(t, prefetched.Creators, creatable{cindex: 8, ctype: basics.AssetCreatable}) @@ -1385,6 +1397,6 @@ func TestEvaluatorPrefetcherAlignmentStateProof(t *testing.T) { requested, prefetched := run(t, l, txn) - prefetched.Accounts[rewardsPool()] = struct{}{} + prefetched.pretend(rewardsPool()) require.Equal(t, requested, prefetched) } diff --git a/ledger/eval/prefetcher/prefetcher_test.go b/ledger/eval/prefetcher/prefetcher_test.go index 1ec96ad3f7..f1b6ab9eda 100644 --- a/ledger/eval/prefetcher/prefetcher_test.go +++ b/ledger/eval/prefetcher/prefetcher_test.go @@ -48,7 +48,10 @@ func makeAddress(addressSeed int) (o basics.Address) { return } -const proto = protocol.ConsensusCurrentVersion +// It would be nice to test current and future, but until that change is made, +// it's better to test future, as that's likely to catch mistakes made while +// developing something new (and likely to catch changes that affect current) +const proto = protocol.ConsensusFuture type lookupError struct{} diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index 7354b4a567..9ac67d2fb7 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -29,10 +29,12 @@ import ( "github.com/algorand/go-algorand/agreement" "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/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/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" @@ -189,15 +191,17 @@ func TestBlockEvaluator(t *testing.T) { err = eval.TestTransactionGroup(txgroup) require.Error(t, err) - validatedBlock, err := eval.GenerateBlock() + unfinishedBlock, err := eval.GenerateBlock(nil) require.NoError(t, err) + validatedBlock := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock(), unfinishedBlock.UnfinishedDeltas()) + accts := genesisInitState.Accounts bal0 := accts[addrs[0]] bal1 := accts[addrs[1]] bal2 := accts[addrs[2]] - l.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) + l.AddValidatedBlock(validatedBlock, agreement.Certificate{}) bal0new, _, _, err := l.LookupAccount(newBlock.Round(), addrs[0]) require.NoError(t, err) @@ -211,6 +215,494 @@ func TestBlockEvaluator(t *testing.T) { require.Equal(t, bal2new.MicroAlgos.Raw, bal2.MicroAlgos.Raw-minFee.Raw) } +// TestPayoutFees ensures that the proper portion of tx fees go to the proposer +func TestPayoutFees(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Lots of balance checks that would be messed up by rewards + genBalances, addrs, _ := ledgertesting.NewTestGenesis(ledgertesting.TurnOffRewards) + payoutsBegin := 40 + ledgertesting.TestConsensusRange(t, payoutsBegin-1, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + proposer := basics.Address{0x01, 0x011} + const eFee = 3_000_000 + dl.txn( + &txntest.Txn{Type: "pay", Sender: addrs[1], + Receiver: proposer, Amount: eFee + 50_000_000}, + ) + + prp := lookup(dl.t, dl.generator, proposer) + require.False(t, prp.IncentiveEligible) + + dl.txn(&txntest.Txn{ + Type: "keyreg", + Sender: proposer, + Fee: eFee, + VotePK: crypto.OneTimeSignatureVerifier{0x01}, + SelectionPK: crypto.VRFVerifier{0x02}, + StateProofPK: merklesignature.Commitment{0x03}, + VoteFirst: 1, VoteLast: 1000, + }) + + prp = lookup(dl.t, dl.generator, proposer) + require.Equal(t, ver >= payoutsBegin, prp.IncentiveEligible) + + dl.fullBlock() // start with an empty block, so no payouts from fees are paid at start of next one + + presink := micros(dl.t, dl.generator, genBalances.FeeSink) + preprop := micros(dl.t, dl.generator, proposer) + t.Log(" presink", presink) + t.Log(" preprop", preprop) + dl.beginBlock() + pay := txntest.Txn{ + Type: "pay", + Sender: addrs[1], + Receiver: addrs[2], + Amount: 100000, + } + dl.txns(&pay, pay.Args("again")) + vb := dl.endBlock(proposer) + + postsink := micros(dl.t, dl.generator, genBalances.FeeSink) + postprop := micros(dl.t, dl.generator, proposer) + t.Log(" postsink", postsink) + t.Log(" postprop", postprop) + + prp = lookup(dl.t, dl.generator, proposer) + + const bonus1 = 10_000_000 // the first bonus value, set in config/consensus.go + if ver >= payoutsBegin { + require.True(t, dl.generator.GenesisProto().Payouts.Enabled) // version sanity check + require.NotZero(t, dl.generator.GenesisProto().Payouts.Percent) // version sanity check + // new fields are in the header + require.EqualValues(t, 2000, vb.Block().FeesCollected.Raw) + require.EqualValues(t, bonus1, vb.Block().Bonus.Raw) + require.EqualValues(t, bonus1+1_500, vb.Block().ProposerPayout().Raw) + // This last one is really only testing the "fake" agreement that + // happens in dl.endBlock(). + require.EqualValues(t, proposer, vb.Block().Proposer()) + + // At the end of the block, part of the fees + bonus have been moved to + // the proposer. + require.EqualValues(t, bonus1+1500, postprop-preprop) // based on 75% in config/consensus.go + require.EqualValues(t, bonus1-500, presink-postsink) + require.Equal(t, prp.LastProposed, dl.generator.Latest()) + } else { + require.False(t, dl.generator.GenesisProto().Payouts.Enabled) + require.Zero(t, dl.generator.GenesisProto().Payouts.Percent) // version sanity check + require.Zero(t, vb.Block().FeesCollected) + require.Zero(t, vb.Block().Bonus) + require.Zero(t, vb.Block().ProposerPayout()) + // fees stayed in the feesink + require.EqualValues(t, 0, postprop-preprop, "%v", proposer) + require.EqualValues(t, 2000, postsink-presink) + require.Zero(t, prp.LastProposed) + } + + // Do another block, make sure proposer doesn't get paid again. (Sanity + // check. The code used to award the payout used to be in block n+1). + vb = dl.fullBlock() + require.Equal(t, postsink, micros(dl.t, dl.generator, genBalances.FeeSink)) + require.Equal(t, postprop, micros(dl.t, dl.generator, proposer)) + + // Rest of the tests only make sense with payout active + if ver < payoutsBegin { + return + } + + // Get the feesink down low, then drain it by proposing. + feesink := vb.Block().FeeSink + data := lookup(t, dl.generator, feesink) + dl.txn(&txntest.Txn{ + Type: "pay", + Sender: feesink, + Receiver: addrs[1], + Amount: data.MicroAlgos.Raw - 12_000_000, + }) + dl.beginBlock() + dl.endBlock(proposer) + require.EqualValues(t, micros(t, dl.generator, feesink), 2_000_000) + + dl.beginBlock() + dl.endBlock(proposer) + require.EqualValues(t, micros(t, dl.generator, feesink), 100_000) + + dl.beginBlock() + dl.endBlock(proposer) + require.EqualValues(t, micros(t, dl.generator, feesink), 100_000) + }) +} + +// TestIncentiveEligible checks that keyreg with extra fee turns on the incentive eligible flag +func TestIncentiveEligible(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + payoutsBegin := 40 + ledgertesting.TestConsensusRange(t, payoutsBegin-1, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + tooSmall := basics.Address{0x01, 0x011} + smallest := basics.Address{0x01, 0x022} + + // They begin ineligible + for _, addr := range []basics.Address{tooSmall, smallest} { + acct, _, _, err := dl.generator.LookupLatest(addr) + require.NoError(t, err) + require.False(t, acct.IncentiveEligible) + } + + // Fund everyone + dl.txns(&txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: tooSmall, Amount: 10_000_000}, + &txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: smallest, Amount: 10_000_000}, + ) + + // Keyreg (but offline) with various fees. No effect on incentive eligible + dl.txns(&txntest.Txn{Type: "keyreg", Sender: tooSmall, Fee: 2_000_000 - 1}, + &txntest.Txn{Type: "keyreg", Sender: smallest, Fee: 2_000_000}, + ) + + for _, addr := range []basics.Address{tooSmall, smallest} { + acct, _, _, err := dl.generator.LookupLatest(addr) + require.NoError(t, err) + require.False(t, acct.IncentiveEligible) + } + + // Keyreg to get online with various fees. Sufficient fee gets `smallest` eligible + keyreg := txntest.Txn{ + Type: "keyreg", + VotePK: crypto.OneTimeSignatureVerifier{0x01}, + SelectionPK: crypto.VRFVerifier{0x02}, + StateProofPK: merklesignature.Commitment{0x03}, + VoteFirst: 1, VoteLast: 1000, + } + tooSmallKR := keyreg + tooSmallKR.Sender = tooSmall + tooSmallKR.Fee = 2_000_000 - 1 + + smallKR := keyreg + smallKR.Sender = smallest + smallKR.Fee = 2_000_000 + dl.txns(&tooSmallKR, &smallKR) + a, _, _, err := dl.generator.LookupLatest(tooSmall) + require.NoError(t, err) + require.False(t, a.IncentiveEligible) + a, _, _, err = dl.generator.LookupLatest(smallest) + require.NoError(t, err) + require.Equal(t, a.IncentiveEligible, ver >= payoutsBegin) + }) +} + +// TestAbsentTracking checks that LastProposed and LastHeartbeat are updated +// properly. +func TestAbsentTracking(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis(func(cfg *ledgertesting.GenesisCfg) { + cfg.OnlineCount = 2 // So we know proposer should propose every 2 rounds, on average + }) + checkingBegins := 40 + ledgertesting.TestConsensusRange(t, checkingBegins-1, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // have addrs[1] go online, which makes it eligible for suspension + dl.txn(&txntest.Txn{ + Type: "keyreg", + Sender: addrs[1], + VotePK: [32]byte{1}, + SelectionPK: [32]byte{1}, + }) + + totals, err := dl.generator.Totals(1) + require.NoError(t, err) + require.NotZero(t, totals.Online.Money.Raw) + + // as configured above, only the first two accounts should be online + require.True(t, lookup(t, dl.generator, addrs[0]).Status == basics.Online) + require.True(t, lookup(t, dl.generator, addrs[1]).Status == basics.Online) + require.False(t, lookup(t, dl.generator, addrs[2]).Status == basics.Online) + // genesis accounts don't begin IncentiveEligible, even if online + require.False(t, lookup(t, dl.generator, addrs[0]).IncentiveEligible) + require.False(t, lookup(t, dl.generator, addrs[1]).IncentiveEligible) + require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + + dl.fullBlock() + + // although it's not even online, we'll use addrs[7] as the proposer + proposer := addrs[7] + dl.beginBlock() + dl.txns(&txntest.Txn{ + Type: "pay", + Sender: addrs[1], + Receiver: addrs[2], + Amount: 100_000, + }) + dl.endBlock(proposer) + + prp := lookup(t, dl.validator, proposer) + if ver >= checkingBegins { + require.Equal(t, prp.LastProposed, dl.validator.Latest()) + } else { + require.Zero(t, prp.LastProposed) + } + require.Zero(t, prp.LastHeartbeat) + require.False(t, prp.IncentiveEligible) + + // addr[1] is spent to an offline account, so Online totals decrease + newtotals, err := dl.generator.Totals(dl.generator.Latest()) + require.NoError(t, err) + // payment and fee left the online account + require.Equal(t, totals.Online.Money.Raw-100_000-1000, newtotals.Online.Money.Raw) + totals = newtotals + + dl.fullBlock() + + // addrs[2] was already offline + dl.txns(&txntest.Txn{Type: "keyreg", Sender: addrs[2]}) // OFFLINE keyreg + regger := lookup(t, dl.validator, addrs[2]) + + // total were unchanged by an offline keyreg from an offline account + newtotals, err = dl.generator.Totals(dl.generator.Latest()) + require.NoError(t, err) + require.Equal(t, totals.Online.Money.Raw, newtotals.Online.Money.Raw) + + // an an offline keyreg transaction records no activity + require.Zero(t, regger.LastProposed) + require.Zero(t, regger.LastHeartbeat) + + // ONLINE keyreg without extra fee + dl.txns(&txntest.Txn{ + Type: "keyreg", + Sender: addrs[2], + VotePK: [32]byte{1}, + SelectionPK: [32]byte{1}, + }) + // online totals have grown + newtotals, err = dl.generator.Totals(dl.generator.Latest()) + require.NoError(t, err) + require.Greater(t, newtotals.Online.Money.Raw, totals.Online.Money.Raw) + + regger = lookup(t, dl.validator, addrs[2]) + require.Zero(t, regger.LastProposed) + require.True(t, regger.Status == basics.Online) + + if ver >= checkingBegins { + require.NotZero(t, regger.LastHeartbeat) // online keyreg caused update + } else { + require.Zero(t, regger.LastHeartbeat) + } + require.False(t, regger.IncentiveEligible) + + // ONLINE keyreg with extra fee + dl.txns(&txntest.Txn{ + Type: "keyreg", + Fee: 2_000_000, + Sender: addrs[2], + VotePK: [32]byte{1}, + SelectionPK: [32]byte{1}, + }) + + regger = lookup(t, dl.validator, addrs[2]) + require.Equal(t, ver >= checkingBegins, regger.IncentiveEligible) + + for i := 0; i < 5; i++ { + dl.fullBlock() + require.True(t, lookup(t, dl.generator, addrs[0]).Status == basics.Online) + require.True(t, lookup(t, dl.generator, addrs[1]).Status == basics.Online) + require.True(t, lookup(t, dl.generator, addrs[2]).Status == basics.Online) + } + + // all are still online after a few blocks + require.True(t, lookup(t, dl.generator, addrs[0]).Status == basics.Online) + require.True(t, lookup(t, dl.generator, addrs[1]).Status == basics.Online) + require.True(t, lookup(t, dl.generator, addrs[2]).Status == basics.Online) + + for i := 0; i < 30; i++ { + dl.fullBlock() + } + + // addrs 0-2 all have about 1/3 of stake, so seemingly (see next block + // of checks) become eligible for suspension after 30 rounds. We're at + // about 35. But, since blocks are empty, nobody's suspendible account + // is noticed. + require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[0]).Status) + require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[1]).Status) + require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[2]).Status) + require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + + // when 2 pays 0, they both get noticed but addr[0] is not considered absent + vb := dl.fullBlock(&txntest.Txn{ + Type: "pay", + Sender: addrs[2], + Receiver: addrs[0], + Amount: 0, + }) + if ver >= checkingBegins { + require.Equal(t, vb.Block().AbsentParticipationAccounts, []basics.Address{addrs[2]}) + } + // addr[0] has never proposed or heartbeat so it is not considered absent + require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[0]).Status) + // addr[1] still hasn't been "noticed" + require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[1]).Status) + require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[2]).Status == basics.Offline) + require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + + // now, when 2 pays 1, 1 gets suspended (unlike 0, we had 1 keyreg early on, so LastHeartbeat>0) + vb = dl.fullBlock(&txntest.Txn{ + Type: "pay", + Sender: addrs[2], + Receiver: addrs[1], + Amount: 0, + }) + if ver >= checkingBegins { + require.Equal(t, vb.Block().AbsentParticipationAccounts, []basics.Address{addrs[1]}) + } + require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[0]).Status) + require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[1]).Status == basics.Offline) + require.False(t, lookup(t, dl.generator, addrs[1]).IncentiveEligible) + require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[2]).Status == basics.Offline) + require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + + // now, addrs[2] proposes, so it gets back online, but stays ineligible + dl.proposer = addrs[2] + dl.fullBlock() + require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[2]).Status) + require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + }) +} + +// TestAbsenteeChallenges ensures that online accounts that don't (do) respond +// to challenges end up off (on) line. +func TestAbsenteeChallenges(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis(func(cfg *ledgertesting.GenesisCfg) { + cfg.OnlineCount = 5 // Make online stake big, so these accounts won't be expected to propose + }) + checkingBegins := 40 + + ledgertesting.TestConsensusRange(t, checkingBegins-1, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // This address ends up being used as a proposer, because that's how we + // jam a specific seed into the block to control the challenge. + // Therefore, it must be an existing account. + seedAndProp := basics.Address{0xaa} + + // We'll generate a challenge for accounts that start with 0xaa. + propguy := basics.Address{0xaa, 0xaa, 0xaa} // Will propose during the challenge window + regguy := basics.Address{0xaa, 0xbb, 0xbb} // Will re-reg during the challenge window + badguy := basics.Address{0xaa, 0x11, 0x11} // Will ignore the challenge + + // Fund them all and have them go online. That makes them eligible to be challenged + for i, guy := range []basics.Address{seedAndProp, propguy, regguy, badguy} { + dl.txns(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: guy, + Amount: 10_000_000, + }, &txntest.Txn{ + Type: "keyreg", + Fee: 5_000_000, // enough to be incentive eligible + Sender: guy, + VotePK: [32]byte{byte(i + 1)}, + SelectionPK: [32]byte{byte(i + 1)}, + }) + acct := lookup(t, dl.generator, guy) + require.Equal(t, basics.Online, acct.Status) + require.Equal(t, ver >= checkingBegins, acct.IncentiveEligible, guy) + } + + for vb := dl.fullBlock(); vb.Block().Round() < 999; vb = dl.fullBlock() { + // we just advancing to one before the challenge round + } + // All still online, same eligibility + for _, guy := range []basics.Address{propguy, regguy, badguy} { + acct := lookup(t, dl.generator, guy) + require.Equal(t, basics.Online, acct.Status) + require.Equal(t, ver >= checkingBegins, acct.IncentiveEligible, guy) + } + // make the BlockSeed start with 0xa in the challenge round + dl.beginBlock() + dl.endBlock(seedAndProp) // This becomes the seed, which is used for the challenge + + for vb := dl.fullBlock(); vb.Block().Round() < 1200; vb = dl.fullBlock() { + // advance through first grace period + } + dl.beginBlock() + dl.endBlock(propguy) // propose, which is a fine (though less likely) way to respond + + // All still online, unchanged eligibility + for _, guy := range []basics.Address{propguy, regguy, badguy} { + acct := lookup(t, dl.generator, guy) + require.Equal(t, basics.Online, acct.Status) + require.Equal(t, ver >= checkingBegins, acct.IncentiveEligible, guy) + } + + for vb := dl.fullBlock(); vb.Block().Round() < 1220; vb = dl.fullBlock() { + // advance into knockoff period. but no transactions means + // unresponsive accounts go unnoticed. + } + // All still online, same eligibility + for _, guy := range []basics.Address{propguy, regguy, badguy} { + acct := lookup(t, dl.generator, guy) + require.Equal(t, basics.Online, acct.Status) + require.Equal(t, ver >= checkingBegins, acct.IncentiveEligible, guy) + } + + // badguy never responded, he gets knocked off when paid + vb := dl.fullBlock(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: badguy, + }) + if ver >= checkingBegins { + require.Equal(t, vb.Block().AbsentParticipationAccounts, []basics.Address{badguy}) + } + acct := lookup(t, dl.generator, badguy) + require.Equal(t, ver >= checkingBegins, basics.Offline == acct.Status) // if checking, badguy fails + require.False(t, acct.IncentiveEligible) + + // propguy proposed during the grace period, he stays on even when paid + dl.txns(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: propguy, + }) + acct = lookup(t, dl.generator, propguy) + require.Equal(t, basics.Online, acct.Status) + require.Equal(t, ver >= checkingBegins, acct.IncentiveEligible) + + // regguy keyregs before he's caught, which is a heartbeat, he stays on as well + dl.txns(&txntest.Txn{ + Type: "keyreg", // Does not pay extra fee, since he's still eligible + Sender: regguy, + VotePK: [32]byte{1}, + SelectionPK: [32]byte{1}, + }) + acct = lookup(t, dl.generator, regguy) + require.Equal(t, basics.Online, acct.Status) + require.Equal(t, ver >= checkingBegins, acct.IncentiveEligible) + dl.txns(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: regguy, + }) + acct = lookup(t, dl.generator, regguy) + require.Equal(t, basics.Online, acct.Status) + require.Equal(t, ver >= checkingBegins, acct.IncentiveEligible) + }) +} + // TestHoldingGet tests some of the corner cases for the asset_holding_get // opcode: the asset doesn't exist, the account doesn't exist, account not opted // in, vs it has none of the asset. This is tested here, even though it should @@ -451,10 +943,11 @@ func TestRekeying(t *testing.T) { return err } } - validatedBlock, err := eval.GenerateBlock() + unfinishedBlock, err := eval.GenerateBlock(nil) if err != nil { return err } + validatedBlock := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock(), unfinishedBlock.UnfinishedDeltas()) backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) defer backlogPool.Shutdown() diff --git a/ledger/evalbench_test.go b/ledger/evalbench_test.go index 371cdad87d..c70795f4d2 100644 --- a/ledger/evalbench_test.go +++ b/ledger/evalbench_test.go @@ -514,16 +514,22 @@ func benchmarkPreparePaymentTransactionsTesting(b *testing.B, numTxns int, txnSo if len(initSignedTxns) > 0 { var numBlocks uint64 = 0 + var unfinishedBlock *ledgercore.UnfinishedBlock var validatedBlock *ledgercore.ValidatedBlock - // there are might more transactions than MaxTxnBytesPerBlock allows - // so make smaller blocks to fit + // there might be more transactions than MaxTxnBytesPerBlock allows so + // make smaller blocks to fit for i, stxn := range initSignedTxns { err := bev.Transaction(stxn, transactions.ApplyData{}) require.NoError(b, err) if maxTxnPerBlock > 0 && i%maxTxnPerBlock == 0 || i == len(initSignedTxns)-1 { - validatedBlock, err = bev.GenerateBlock() + unfinishedBlock, err = bev.GenerateBlock(nil) require.NoError(b, err) + // We are not setting seed & proposer details with + // FinishBlock/WithProposer. When agreement actually does that, + // it surely has some cost. + vb := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock(), unfinishedBlock.UnfinishedDeltas()) + validatedBlock = &vb for _, l := range []*Ledger{l, l2} { err = l.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) require.NoError(b, err) @@ -562,12 +568,14 @@ func benchmarkPreparePaymentTransactionsTesting(b *testing.B, numTxns int, txnSo require.NoError(b, err) } - validatedBlock, err := bev.GenerateBlock() + // as above - this might be an underestimate because we skip agreement + unfinishedBlock, err := bev.GenerateBlock(nil) require.NoError(b, err) + validatedBlock := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock(), unfinishedBlock.UnfinishedDeltas()) blockBuildDone := time.Now() blockBuildTime := blockBuildDone.Sub(setupDone) b.ReportMetric(float64(blockBuildTime)/float64(numTxns), "ns/block_build_tx") - return validatedBlock + return &validatedBlock } diff --git a/ledger/fullblock_perf_test.go b/ledger/fullblock_perf_test.go index c45963cb1c..b0fef304f3 100644 --- a/ledger/fullblock_perf_test.go +++ b/ledger/fullblock_perf_test.go @@ -314,12 +314,12 @@ func addTransaction(bc *benchConfig, stxn transactions.SignedTxn) uint64 { } func addBlock(bc *benchConfig) { - vblk, err := bc.eval.GenerateBlock() + vblk, err := bc.eval.GenerateBlock(nil) cert := agreement.Certificate{} require.NoError(bc.b, err) - bc.blocks = append(bc.blocks, vblk.Block()) + bc.blocks = append(bc.blocks, vblk.UnfinishedBlock()) - err = bc.l0.AddBlock(vblk.Block(), cert) + err = bc.l0.AddBlock(vblk.UnfinishedBlock(), cert) require.NoError(bc.b, err) _, last := bc.l0.LatestCommitted() diff --git a/ledger/ledger_perf_test.go b/ledger/ledger_perf_test.go index f160838ab1..b34877aed5 100644 --- a/ledger/ledger_perf_test.go +++ b/ledger/ledger_perf_test.go @@ -293,24 +293,24 @@ func benchmarkFullBlocks(params testParams, b *testing.B) { } } - lvb, err := eval.GenerateBlock() + lvb, err := eval.GenerateBlock(nil) require.NoError(b, err) // If this is the app creation block, add to both ledgers if i == 1 { - err = l0.AddBlock(lvb.Block(), cert) + err = l0.AddBlock(lvb.UnfinishedBlock(), cert) require.NoError(b, err) - err = l1.AddBlock(lvb.Block(), cert) + err = l1.AddBlock(lvb.UnfinishedBlock(), cert) require.NoError(b, err) continue } // For all other blocks, add just to the first ledger, and stash // away to be replayed in the second ledger while running timer - err = l0.AddBlock(lvb.Block(), cert) + err = l0.AddBlock(lvb.UnfinishedBlock(), cert) require.NoError(b, err) - blocks = append(blocks, lvb.Block()) + blocks = append(blocks, lvb.UnfinishedBlock()) } b.Logf("built %d blocks, each with %d txns", numBlocks, txPerBlock) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index b18428741b..8a855ceadd 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -71,7 +71,7 @@ func (l *Ledger) appendUnvalidated(blk bookkeeping.Block) error { l.verifiedTxnCache = verify.GetMockedCache(false) vb, err := l.Validate(context.Background(), blk, backlogPool) if err != nil { - return fmt.Errorf("appendUnvalidated error in Validate: %s", err.Error()) + return fmt.Errorf("appendUnvalidated error in Validate: %w", err) } return l.AddValidatedBlock(*vb, agreement.Certificate{}) @@ -100,6 +100,22 @@ func initNextBlockHeader(correctHeader *bookkeeping.BlockHeader, lastBlock bookk } } +// endOfBlock is simplified implementation of BlockEvaluator.endOfBlock so that +// our test blocks can pass validation. +func endOfBlock(blk *bookkeeping.Block) error { + if blk.ConsensusProtocol().Payouts.Enabled { + // This won't work for inner fees, and it's not bothering with overflow + for _, txn := range blk.Payset { + blk.FeesCollected.Raw += txn.Txn.Fee.Raw + } + // blk.ProposerPayout is allowed to be zero, so don't reproduce the calc here. + blk.BlockHeader.Proposer = basics.Address{0x01} // Must be set to _something_. + } + var err error + blk.TxnCommitments, err = blk.PaysetCommit() + return err +} + func makeNewEmptyBlock(t *testing.T, l *Ledger, GenesisID string, initAccounts map[basics.Address]basics.AccountData) (blk bookkeeping.Block) { a := require.New(t) @@ -126,13 +142,14 @@ func makeNewEmptyBlock(t *testing.T, l *Ledger, GenesisID string, initAccounts m a.NoError(err, "could not get incentive pool balance") blk.BlockHeader = bookkeeping.BlockHeader{ - GenesisID: GenesisID, - Round: l.Latest() + 1, - Branch: lastBlock.Hash(), + Round: l.Latest() + 1, + Branch: lastBlock.Hash(), + // Seed: does not matter, TimeStamp: 0, + GenesisID: GenesisID, + Bonus: bookkeeping.NextBonus(lastBlock.BlockHeader, &proto), RewardsState: lastBlock.NextRewardsState(l.Latest()+1, proto, poolBal.MicroAlgos, totalRewardUnits, logging.Base()), UpgradeState: lastBlock.UpgradeState, - // Seed: does not matter, // UpgradeVote: empty, } @@ -170,12 +187,11 @@ func (l *Ledger) appendUnvalidatedSignedTx(t *testing.T, initAccounts map[basics if err != nil { return fmt.Errorf("could not sign txn: %s", err.Error()) } + blk.Payset = append(blk.Payset, txib) if proto.TxnCounter { blk.TxnCounter = blk.TxnCounter + 1 } - blk.Payset = append(blk.Payset, txib) - blk.TxnCommitments, err = blk.PaysetCommit() - require.NoError(t, err) + require.NoError(t, endOfBlock(&blk)) return l.appendUnvalidated(blk) } @@ -241,13 +257,13 @@ func TestLedgerBlockHeaders(t *testing.T) { a.NoError(err, "could not get incentive pool balance") correctHeader := bookkeeping.BlockHeader{ - GenesisID: t.Name(), - Round: l.Latest() + 1, - Branch: lastBlock.Hash(), + Round: l.Latest() + 1, + Branch: lastBlock.Hash(), + // Seed: does not matter, TimeStamp: 0, + GenesisID: t.Name(), RewardsState: lastBlock.NextRewardsState(l.Latest()+1, proto, poolBal.MicroAlgos, totalRewardUnits, logging.Base()), UpgradeState: lastBlock.UpgradeState, - // Seed: does not matter, // UpgradeVote: empty, } @@ -270,55 +286,72 @@ func TestLedgerBlockHeaders(t *testing.T) { badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.Round++ - a.Error(l.appendUnvalidated(badBlock), "added block header with round that was too high") + a.ErrorContains(l.appendUnvalidated(badBlock), "ledger does not have entry") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.Round-- - a.Error(l.appendUnvalidated(badBlock), "added block header with round that was too low") + a.ErrorIs(l.appendUnvalidated(badBlock), eval.ErrRoundZero) badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.Round = 0 - a.Error(l.appendUnvalidated(badBlock), "added block header with round 0") + a.ErrorIs(l.appendUnvalidated(badBlock), eval.ErrRoundZero) badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.GenesisID = "" - a.Error(l.appendUnvalidated(badBlock), "added block header with empty genesis ID") + a.ErrorContains(l.appendUnvalidated(badBlock), "genesis ID missing") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.GenesisID = "incorrect" - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect genesis ID") + a.ErrorContains(l.appendUnvalidated(badBlock), "genesis ID mismatch") + + badBlock = bookkeeping.Block{BlockHeader: correctHeader} + badBlock.BlockHeader.UpgradePropose = "invalid" + a.ErrorContains(l.appendUnvalidated(badBlock), "proposed upgrade wait rounds 0") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.UpgradePropose = "invalid" - a.Error(l.appendUnvalidated(badBlock), "added block header with invalid upgrade proposal") + badBlock.BlockHeader.UpgradeDelay = 20000 + a.ErrorContains(l.appendUnvalidated(badBlock), "UpgradeState mismatch") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.UpgradeApprove = true - a.Error(l.appendUnvalidated(badBlock), "added block header with upgrade approve set but no open upgrade") + a.ErrorContains(l.appendUnvalidated(badBlock), "approval without an active proposal") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.CurrentProtocol = "incorrect" - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect current protocol") + a.ErrorContains(l.appendUnvalidated(badBlock), "protocol not supported") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.CurrentProtocol = "" - a.Error(l.appendUnvalidated(badBlock), "added block header with empty current protocol") + a.ErrorContains(l.appendUnvalidated(badBlock), "protocol not supported", "header with empty current protocol") + + badBlock = bookkeeping.Block{BlockHeader: correctHeader} + var wrongVersion protocol.ConsensusVersion + for ver := range config.Consensus { + if ver != correctHeader.CurrentProtocol { + wrongVersion = ver + break + } + } + a.NotEmpty(wrongVersion) + badBlock.BlockHeader.CurrentProtocol = wrongVersion + a.ErrorContains(l.appendUnvalidated(badBlock), "UpgradeState mismatch") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.NextProtocol = "incorrect" - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect next protocol") + a.ErrorContains(l.appendUnvalidated(badBlock), "UpgradeState mismatch", "added block header with incorrect next protocol") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.NextProtocolApprovals++ - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect number of upgrade approvals") + a.ErrorContains(l.appendUnvalidated(badBlock), "UpgradeState mismatch", "added block header with incorrect number of upgrade approvals") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.NextProtocolVoteBefore++ - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect next protocol vote deadline") + a.ErrorContains(l.appendUnvalidated(badBlock), "UpgradeState mismatch", "added block header with incorrect next protocol vote deadline") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.NextProtocolSwitchOn++ - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect next protocol switch round") + a.ErrorContains(l.appendUnvalidated(badBlock), "UpgradeState mismatch", "added block header with incorrect next protocol switch round") // TODO test upgrade cases with a valid upgrade in progress @@ -326,33 +359,33 @@ func TestLedgerBlockHeaders(t *testing.T) { badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.Branch = bookkeeping.BlockHash{} - a.Error(l.appendUnvalidated(badBlock), "added block header with empty previous-block hash") + a.ErrorContains(l.appendUnvalidated(badBlock), "block branch incorrect") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.Branch[0]++ - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect previous-block hash") + a.ErrorContains(l.appendUnvalidated(badBlock), "block branch incorrect") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.RewardsLevel++ - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect rewards level") + a.ErrorContains(l.appendUnvalidated(badBlock), "bad rewards state") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.RewardsRate++ - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect rewards rate") + a.ErrorContains(l.appendUnvalidated(badBlock), "bad rewards state") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.RewardsResidue++ - a.Error(l.appendUnvalidated(badBlock), "added block header with incorrect rewards residue") + a.ErrorContains(l.appendUnvalidated(badBlock), "bad rewards state") // TODO test rewards cases with changing poolAddr money, with changing round, and with changing total reward units badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.TxnCommitments.NativeSha512_256Commitment = crypto.Hash([]byte{0}) - a.Error(l.appendUnvalidated(badBlock), "added block header with empty transaction root") + a.ErrorContains(l.appendUnvalidated(badBlock), "txn root wrong") badBlock = bookkeeping.Block{BlockHeader: correctHeader} badBlock.BlockHeader.TxnCommitments.NativeSha512_256Commitment[0]++ - a.Error(l.appendUnvalidated(badBlock), "added block header with invalid transaction root") + a.ErrorContains(l.appendUnvalidated(badBlock), "txn root wrong") correctBlock := bookkeeping.Block{BlockHeader: correctHeader} a.NoError(l.appendUnvalidated(correctBlock), "could not add block with correct header") @@ -655,42 +688,36 @@ func TestLedgerSingleTxV24(t *testing.T) { badTx = correctAssetConfig badTx.ConfigAsset = 2 err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "asset 2 does not exist or has been deleted") + a.ErrorContains(err, "asset 2 does not exist or has been deleted") badTx = correctAssetConfig badTx.ConfigAsset = assetIdx badTx.AssetFrozen = true err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "type acfg has non-zero fields for type afrz") + a.ErrorContains(err, "type acfg has non-zero fields for type afrz") badTx = correctAssetConfig badTx.ConfigAsset = assetIdx badTx.Sender = addrList[1] badTx.AssetParams.Freeze = addrList[0] err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "this transaction should be issued by the manager") + a.ErrorContains(err, "this transaction should be issued by the manager") badTx = correctAssetConfig badTx.AssetParams.UnitName = "very long unit name that exceeds the limit" err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "transaction asset unit name too big: 42 > 8") + a.ErrorContains(err, "transaction asset unit name too big: 42 > 8") badTx = correctAssetTransfer badTx.XferAsset = assetIdx badTx.AssetAmount = 101 err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "underflow on subtracting 101 from sender amount 100") + a.ErrorContains(err, "underflow on subtracting 101 from sender amount 100") badTx = correctAssetTransfer badTx.XferAsset = assetIdx err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), fmt.Sprintf("asset %d missing from", assetIdx)) + a.ErrorContains(err, fmt.Sprintf("asset %d missing from", assetIdx)) a.NoError(l.appendUnvalidatedTx(t, initAccounts, initSecrets, correctAppCreate, ad)) appIdx = 2 // the second successful txn @@ -700,24 +727,20 @@ func TestLedgerSingleTxV24(t *testing.T) { program[0] = '\x01' badTx.ApprovalProgram = program err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "program version must be >= 2") + a.ErrorContains(err, "program version must be >= 2") badTx = correctAppCreate badTx.ApplicationID = appIdx err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "programs may only be specified during application creation or update") + a.ErrorContains(err, "programs may only be specified during application creation or update") badTx = correctAppCall badTx.ApplicationID = 0 err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "ApprovalProgram: invalid program (empty)") + a.ErrorContains(err, "ApprovalProgram: invalid program (empty)") badTx.ApprovalProgram = []byte{242} err = l.appendUnvalidatedTx(t, initAccounts, initSecrets, badTx, ad) - a.Error(err) - a.Contains(err.Error(), "ApprovalProgram: invalid version") + a.ErrorContains(err, "ApprovalProgram: invalid version") correctAppCall.ApplicationID = appIdx a.NoError(l.appendUnvalidatedTx(t, initAccounts, initSecrets, correctAppCall, ad)) @@ -1248,13 +1271,14 @@ func testLedgerSingleTxApplyData(t *testing.T, version protocol.ConsensusVersion a.NoError(err, "could not get last block") correctHeader := bookkeeping.BlockHeader{ - GenesisID: t.Name(), - Round: l.Latest() + 1, - Branch: lastBlock.Hash(), + Round: l.Latest() + 1, + Branch: lastBlock.Hash(), + // Seed: does not matter, TimeStamp: 0, + GenesisID: t.Name(), + Bonus: bookkeeping.NextBonus(lastBlock.BlockHeader, &proto), RewardsState: lastBlock.NextRewardsState(l.Latest()+1, proto, poolBal.MicroAlgos, totalRewardUnits, logging.Base()), UpgradeState: lastBlock.UpgradeState, - // Seed: does not matter, // UpgradeVote: empty, } correctHeader.RewardsPool = testPoolAddr @@ -1267,8 +1291,7 @@ func testLedgerSingleTxApplyData(t *testing.T, version protocol.ConsensusVersion initNextBlockHeader(&correctHeader, lastBlock, proto) correctBlock := bookkeeping.Block{BlockHeader: correctHeader} - correctBlock.TxnCommitments, err = correctBlock.PaysetCommit() - a.NoError(err) + a.NoError(endOfBlock(&correctBlock)) a.NoError(l.appendUnvalidated(correctBlock), "could not add block with correct header") } diff --git a/ledger/ledgercore/accountdata.go b/ledger/ledgercore/accountdata.go index 3685d16909..92efa394a1 100644 --- a/ledger/ledgercore/accountdata.go +++ b/ledger/ledgercore/accountdata.go @@ -39,6 +39,7 @@ type AccountBaseData struct { RewardsBase uint64 RewardedMicroAlgos basics.MicroAlgos AuthAddr basics.Address + IncentiveEligible bool TotalAppSchema basics.StateSchema // Totals across created globals, and opted in locals. TotalExtraAppPages uint32 // Total number of extra pages across all created apps @@ -48,6 +49,9 @@ type AccountBaseData struct { TotalAssets uint64 // Total of asset creations and optins (i.e. number of holdings) TotalBoxes uint64 // Total number of boxes associated to this account TotalBoxBytes uint64 // Total bytes for this account's boxes. keys _and_ values count + + LastProposed basics.Round // The last round that this account proposed the winning block. + LastHeartbeat basics.Round // The last round that this account sent a heartbeat to show it was online. } // VotingData holds participation information @@ -65,6 +69,7 @@ type VotingData struct { type OnlineAccountData struct { MicroAlgosWithRewards basics.MicroAlgos VotingData + IncentiveEligible bool } // ToAccountData returns ledgercore.AccountData from basics.AccountData @@ -75,8 +80,8 @@ func ToAccountData(acct basics.AccountData) AccountData { MicroAlgos: acct.MicroAlgos, RewardsBase: acct.RewardsBase, RewardedMicroAlgos: acct.RewardedMicroAlgos, - - AuthAddr: acct.AuthAddr, + AuthAddr: acct.AuthAddr, + IncentiveEligible: acct.IncentiveEligible, TotalAppSchema: acct.TotalAppSchema, TotalExtraAppPages: acct.TotalExtraAppPages, @@ -86,6 +91,9 @@ func ToAccountData(acct basics.AccountData) AccountData { TotalAppLocalStates: uint64(len(acct.AppLocalStates)), TotalBoxes: acct.TotalBoxes, TotalBoxBytes: acct.TotalBoxBytes, + + LastProposed: acct.LastProposed, + LastHeartbeat: acct.LastHeartbeat, }, VotingData: VotingData{ VoteID: acct.VoteID, @@ -105,6 +113,8 @@ func AssignAccountData(a *basics.AccountData, acct AccountData) { a.MicroAlgos = acct.MicroAlgos a.RewardsBase = acct.RewardsBase a.RewardedMicroAlgos = acct.RewardedMicroAlgos + a.AuthAddr = acct.AuthAddr + a.IncentiveEligible = acct.IncentiveEligible a.VoteID = acct.VoteID a.SelectionID = acct.SelectionID @@ -113,11 +123,13 @@ func AssignAccountData(a *basics.AccountData, acct AccountData) { a.VoteLastValid = acct.VoteLastValid a.VoteKeyDilution = acct.VoteKeyDilution - a.AuthAddr = acct.AuthAddr a.TotalAppSchema = acct.TotalAppSchema a.TotalExtraAppPages = acct.TotalExtraAppPages a.TotalBoxes = acct.TotalBoxes a.TotalBoxBytes = acct.TotalBoxBytes + + a.LastProposed = acct.LastProposed + a.LastHeartbeat = acct.LastHeartbeat } // WithUpdatedRewards calls basics account data WithUpdatedRewards @@ -134,10 +146,23 @@ func (u *AccountData) ClearOnlineState() { u.VotingData = VotingData{} } +// Suspend sets the status to Offline, but does _not_ clear voting keys, so +// that a heartbeat can bring the account back Online +func (u *AccountData) Suspend() { + u.Status = basics.Offline + // To regain eligibility, the account will have to `keyreg` with the extra fee. + u.IncentiveEligible = false +} + +// Suspended returns true if the account is suspended (offline with keys) +func (u *AccountData) Suspended() bool { + return u.Status == basics.Offline && !u.VoteID.IsEmpty() +} + // MinBalance computes the minimum balance requirements for an account based on // some consensus parameters. MinBalance should correspond roughly to how much // storage the account is allowed to store on disk. -func (u AccountData) MinBalance(proto *config.ConsensusParams) (res basics.MicroAlgos) { +func (u AccountData) MinBalance(proto *config.ConsensusParams) basics.MicroAlgos { return basics.MinBalance( proto, uint64(u.TotalAssets), @@ -148,6 +173,15 @@ func (u AccountData) MinBalance(proto *config.ConsensusParams) (res basics.Micro ) } +// AvailableBalance returns the amount of MicroAlgos that are available for +// spending without fully closing the account. +func (u AccountData) AvailableBalance(proto *config.ConsensusParams) basics.MicroAlgos { + if left, o := basics.OSubA(u.MicroAlgos, u.MinBalance(proto)); !o { + return left + } + return basics.MicroAlgos{} +} + // IsZero checks if an AccountData value is the same as its zero value. func (u AccountData) IsZero() bool { return u == AccountData{} @@ -179,6 +213,7 @@ func (u AccountData) OnlineAccountData(proto config.ConsensusParams, rewardsLeve VoteLastValid: u.VoteLastValid, VoteKeyDilution: u.VoteKeyDilution, }, + IncentiveEligible: u.IncentiveEligible, } } diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index b735d391fe..1d2562ca4f 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -279,7 +279,7 @@ func (ad *AccountDeltas) Hydrate() { } } -// Dehydrate normalized the fields of this AccountDeltas, and clears any redundant internal caching. +// Dehydrate normalizes the fields of this AccountDeltas, and clears any redundant internal caching. // This is useful for comparing AccountDeltas objects for equality. func (ad *AccountDeltas) Dehydrate() { if ad.Accts == nil { diff --git a/ledger/ledgercore/validatedBlock.go b/ledger/ledgercore/validatedBlock.go index 541a3a54f8..0d7704710d 100644 --- a/ledger/ledgercore/validatedBlock.go +++ b/ledger/ledgercore/validatedBlock.go @@ -17,6 +17,7 @@ package ledgercore import ( + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/committee" ) @@ -39,17 +40,6 @@ func (vb ValidatedBlock) Delta() StateDelta { return vb.delta } -// WithSeed returns a copy of the ValidatedBlock with a modified seed. -func (vb ValidatedBlock) WithSeed(s committee.Seed) ValidatedBlock { - newblock := vb.blk - newblock.BlockHeader.Seed = s - - return ValidatedBlock{ - blk: newblock, - delta: vb.delta, - } -} - // MakeValidatedBlock creates a validated block. func MakeValidatedBlock(blk bookkeeping.Block, delta StateDelta) ValidatedBlock { return ValidatedBlock{ @@ -57,3 +47,54 @@ func MakeValidatedBlock(blk bookkeeping.Block, delta StateDelta) ValidatedBlock delta: delta, } } + +// UnfinishedBlock represents a block that has been generated, but is +// not yet ready for proposing until FinishBlock is called. +type UnfinishedBlock struct { + finalAccounts map[basics.Address]AccountData // status of selected accounts at end of block + blk bookkeeping.Block + deltas StateDelta +} + +// MakeUnfinishedBlock creates an unfinished block. +func MakeUnfinishedBlock(blk bookkeeping.Block, deltas StateDelta, finalAccounts map[basics.Address]AccountData) UnfinishedBlock { + return UnfinishedBlock{ + finalAccounts: finalAccounts, + blk: blk, + deltas: deltas, + } +} + +// UnfinishedBlock returns the underlying Block. It should only be used for statistics and testing purposes, +// as the block is not yet finished and ready for proposing. +func (ub UnfinishedBlock) UnfinishedBlock() bookkeeping.Block { + return ub.blk +} + +// UnfinishedDeltas returns the unfinished deltas. It should only be used for statistics and testing purposes, +// as the block is not yet finished and ready for proposing. +func (ub UnfinishedBlock) UnfinishedDeltas() StateDelta { + return ub.deltas +} + +// ContainsAddress returns true if the balance data about the given address is present in the unfinished block. +func (ub UnfinishedBlock) ContainsAddress(addr basics.Address) bool { + _, ok := ub.finalAccounts[addr] + return ok +} + +// FinishBlock completes the block and returns a proposable block. +func (ub UnfinishedBlock) FinishBlock(s committee.Seed, proposer basics.Address, eligible bool) bookkeeping.Block { + // Look up the given proposer's balance by the end of this block + propData, ok := ub.finalAccounts[proposer] + // This proposer has closed their account and is not eligible for rewards + if !ok || propData.MicroAlgos.IsZero() { + eligible = false + } + return ub.blk.WithProposer(s, proposer, eligible) +} + +// Round returns the round of the block. +func (ub UnfinishedBlock) Round() basics.Round { + return ub.blk.Round() +} diff --git a/ledger/lruonlineaccts_test.go b/ledger/lruonlineaccts_test.go index 0f3bc81712..fb37867802 100644 --- a/ledger/lruonlineaccts_test.go +++ b/ledger/lruonlineaccts_test.go @@ -39,10 +39,13 @@ func TestLRUOnlineAccountsBasic(t *testing.T) { // write 50 accounts for i := 0; i < accountsNum; i++ { acct := trackerdb.PersistedOnlineAccountData{ - Addr: basics.Address(crypto.Hash([]byte{byte(i)})), - Round: basics.Round(i), - Ref: mockEntryRef{int64(i)}, - AccountData: trackerdb.BaseOnlineAccountData{MicroAlgos: basics.MicroAlgos{Raw: uint64(i)}}, + Addr: basics.Address(crypto.Hash([]byte{byte(i)})), + Round: basics.Round(i), + Ref: mockEntryRef{int64(i)}, + AccountData: trackerdb.BaseOnlineAccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(i)}, + IncentiveEligible: i%2 == 0, + }, } baseOnlineAcct.write(acct) } @@ -55,6 +58,7 @@ func TestLRUOnlineAccountsBasic(t *testing.T) { require.Equal(t, basics.Round(i), acct.Round) require.Equal(t, addr, acct.Addr) require.Equal(t, uint64(i), acct.AccountData.MicroAlgos.Raw) + require.Equal(t, i%2 == 0, acct.AccountData.IncentiveEligible) require.Equal(t, mockEntryRef{int64(i)}, acct.Ref) } @@ -79,6 +83,7 @@ func TestLRUOnlineAccountsBasic(t *testing.T) { require.Equal(t, basics.Round(i), acct.Round) require.Equal(t, addr, acct.Addr) require.Equal(t, uint64(i), acct.AccountData.MicroAlgos.Raw) + require.Equal(t, i%2 == 0, acct.AccountData.IncentiveEligible) require.Equal(t, mockEntryRef{int64(i)}, acct.Ref) } else { require.False(t, has) diff --git a/ledger/simple_test.go b/ledger/simple_test.go index 10b87f9378..8af40eaaf3 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -17,6 +17,7 @@ package ledger import ( + "context" "fmt" "strings" "testing" @@ -26,7 +27,9 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/verify" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -100,6 +103,9 @@ func fillDefaults(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator, txn * if txn.FirstValid == 0 { txn.FirstValid = eval.Round() } + if txn.Type == protocol.KeyRegistrationTx && txn.VoteFirst == 0 { + txn.VoteFirst = eval.Round() + } txn.FillDefaults(ledger.GenesisProto()) } @@ -136,11 +142,43 @@ func txgroup(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator, txns ...*t return eval.TransactionGroup(transactions.WrapSignedTxnsWithAD(txgroup)) } -// endBlock completes the block being created, returns the ValidatedBlock for inspection -func endBlock(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator) *ledgercore.ValidatedBlock { - validatedBlock, err := eval.GenerateBlock() +// endBlock completes the block being created, returning the ValidatedBlock for +// inspection. Proposer is optional - if unset, blocks will be finished with +// ZeroAddress proposer. +func endBlock(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator, proposer ...basics.Address) *ledgercore.ValidatedBlock { + ub, err := eval.GenerateBlock(nil) + require.NoError(t, err) + + // We fake some thigns that agreement would do, like setting proposer + validatedBlock := ledgercore.MakeValidatedBlock(ub.UnfinishedBlock(), ub.UnfinishedDeltas()) + gvb := &validatedBlock + + // Making the proposer the feesink unless specified causes less disruption + // to existing tests. (Because block payouts don't change balances.) + prp := gvb.Block().BlockHeader.FeeSink + if len(proposer) > 0 { + prp = proposer[0] + } + + // Since we can't do agreement, we have this backdoor way to install a + // proposer or seed into the header for tests. Doesn't matter that it makes + // them both the same. Since this can't call the agreement code, the + // eligibility of the prp is not considered. + if ledger.GenesisProto().Payouts.Enabled { + *gvb = ledgercore.MakeValidatedBlock(gvb.Block().WithProposer(committee.Seed(prp), prp, true), gvb.Delta()) + } else { + // To more closely mimic the agreement code, we don't + // write the proposer when !Payouts.Enabled. + *gvb = ledgercore.MakeValidatedBlock(gvb.Block().WithProposer(committee.Seed(prp), basics.Address{}, false), gvb.Delta()) + } + + vvb, err := validateWithoutSignatures(t, ledger, gvb.Block()) require.NoError(t, err) - err = ledger.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) + + // we could add some checks that ensure gvb and vvb are quite similar, but + // they will differ a bit, as noted above. + + err = ledger.AddValidatedBlock(*vvb, agreement.Certificate{}) require.NoError(t, err) // `rndBQ` gives the latest known block round added to the ledger // we should wait until `rndBQ` block to be committed to blockQueue, @@ -152,7 +190,14 @@ func endBlock(t testing.TB, ledger *Ledger, eval *eval.BlockEvaluator) *ledgerco // then we return the result and continue the execution. rndBQ := ledger.Latest() ledger.WaitForCommit(rndBQ) - return validatedBlock + return vvb +} + +func validateWithoutSignatures(t testing.TB, ledger *Ledger, blk bookkeeping.Block) (*ledgercore.ValidatedBlock, error) { + save := ledger.verifiedTxnCache + defer func() { ledger.verifiedTxnCache = save }() + ledger.verifiedTxnCache = verify.GetMockedCache(true) // validate the txns, but not signatures + return ledger.Validate(context.Background(), blk, nil) } // main wraps up some TEAL source in a header and footer so that it is diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index 0a33e5f12b..c7c722686d 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -197,12 +197,15 @@ func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.Si } // Finally, process any pending end-of-block state changes. - vb, err := eval.GenerateBlock() + ub, err := eval.GenerateBlock(nil) if err != nil { return nil, err } - return vb, nil + // Since we skip agreement, this block is imperfect w/ respect to seed/proposer/payouts + vb := ledgercore.MakeValidatedBlock(ub.UnfinishedBlock(), ub.UnfinishedDeltas()) + + return &vb, nil } func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer logic.EvalTracer, overrides ResultEvalOverrides) (*ledgercore.ValidatedBlock, error) { diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index ae43fe72fc..3a6bbe0edf 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -104,11 +104,13 @@ func (env *Environment) nextBlock() *eval.BlockEvaluator { // endBlock completes the block being created, returns the ValidatedBlock for inspection func (env *Environment) endBlock(evaluator *eval.BlockEvaluator) *ledgercore.ValidatedBlock { env.t.Helper() - validatedBlock, err := evaluator.GenerateBlock() + unfinishedBlock, err := evaluator.GenerateBlock(nil) require.NoError(env.t, err) - err = env.Ledger.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) + // Since we skip agreement, this block is imperfect w/ respect to seed/proposer/payouts + validatedBlock := ledgercore.MakeValidatedBlock(unfinishedBlock.UnfinishedBlock(), unfinishedBlock.UnfinishedDeltas()) + err = env.Ledger.AddValidatedBlock(validatedBlock, agreement.Certificate{}) require.NoError(env.t, err) - return validatedBlock + return &validatedBlock } // Txn creates and executes a new block with the given transaction and returns its ApplyData diff --git a/ledger/store/trackerdb/data.go b/ledger/store/trackerdb/data.go index fc243b2b92..da2cf3eeab 100644 --- a/ledger/store/trackerdb/data.go +++ b/ledger/store/trackerdb/data.go @@ -47,6 +47,9 @@ type BaseAccountData struct { TotalAppLocalStates uint64 `codec:"l"` TotalBoxes uint64 `codec:"m"` TotalBoxBytes uint64 `codec:"n"` + IncentiveEligible bool `codec:"o"` + LastProposed basics.Round `codec:"p"` + LastHeartbeat basics.Round `codec:"q"` BaseVotingData @@ -149,8 +152,9 @@ type BaseOnlineAccountData struct { BaseVotingData - MicroAlgos basics.MicroAlgos `codec:"Y"` - RewardsBase uint64 `codec:"Z"` + IncentiveEligible bool `codec:"X"` + MicroAlgos basics.MicroAlgos `codec:"Y"` + RewardsBase uint64 `codec:"Z"` } // PersistedKVData represents the stored entry behind a application boxed key/value. @@ -286,6 +290,10 @@ func (ba *BaseAccountData) SetCoreAccountData(ad *ledgercore.AccountData) { ba.TotalAppLocalStates = ad.TotalAppLocalStates ba.TotalBoxes = ad.TotalBoxes ba.TotalBoxBytes = ad.TotalBoxBytes + ba.IncentiveEligible = ad.IncentiveEligible + + ba.LastProposed = ad.LastProposed + ba.LastHeartbeat = ad.LastHeartbeat ba.BaseVotingData.SetCoreAccountData(ad) } @@ -306,6 +314,10 @@ func (ba *BaseAccountData) SetAccountData(ad *basics.AccountData) { ba.TotalAppLocalStates = uint64(len(ad.AppLocalStates)) ba.TotalBoxes = ad.TotalBoxes ba.TotalBoxBytes = ad.TotalBoxBytes + ba.IncentiveEligible = ad.IncentiveEligible + + ba.LastProposed = ad.LastProposed + ba.LastHeartbeat = ad.LastHeartbeat ba.BaseVotingData.VoteID = ad.VoteID ba.BaseVotingData.SelectionID = ad.SelectionID @@ -342,6 +354,10 @@ func (ba *BaseAccountData) GetLedgerCoreAccountBaseData() ledgercore.AccountBase TotalAssets: ba.TotalAssets, TotalBoxes: ba.TotalBoxes, TotalBoxBytes: ba.TotalBoxBytes, + IncentiveEligible: ba.IncentiveEligible, + + LastProposed: ba.LastProposed, + LastHeartbeat: ba.LastHeartbeat, } } @@ -365,6 +381,7 @@ func (ba *BaseAccountData) GetAccountData() basics.AccountData { RewardsBase: ba.RewardsBase, RewardedMicroAlgos: ba.RewardedMicroAlgos, AuthAddr: ba.AuthAddr, + IncentiveEligible: ba.IncentiveEligible, TotalAppSchema: basics.StateSchema{ NumUint: ba.TotalAppSchemaNumUint, NumByteSlice: ba.TotalAppSchemaNumByteSlice, @@ -379,6 +396,9 @@ func (ba *BaseAccountData) GetAccountData() basics.AccountData { VoteFirstValid: ba.VoteFirstValid, VoteLastValid: ba.VoteLastValid, VoteKeyDilution: ba.VoteKeyDilution, + + LastProposed: ba.LastProposed, + LastHeartbeat: ba.LastHeartbeat, } } @@ -389,6 +409,7 @@ func (ba *BaseAccountData) IsEmpty() bool { ba.RewardsBase == 0 && ba.RewardedMicroAlgos.Raw == 0 && ba.AuthAddr.IsZero() && + !ba.IncentiveEligible && ba.TotalAppSchemaNumUint == 0 && ba.TotalAppSchemaNumByteSlice == 0 && ba.TotalExtraAppPages == 0 && @@ -398,6 +419,8 @@ func (ba *BaseAccountData) IsEmpty() bool { ba.TotalAppLocalStates == 0 && ba.TotalBoxes == 0 && ba.TotalBoxBytes == 0 && + ba.LastProposed == 0 && + ba.LastHeartbeat == 0 && ba.BaseVotingData.IsEmpty() } @@ -421,11 +444,11 @@ func (bo *BaseOnlineAccountData) IsVotingEmpty() bool { return bo.BaseVotingData.IsEmpty() } -// IsEmpty return true if any of the fields are non-zero. +// IsEmpty return true if all of the fields are zero. func (bo *BaseOnlineAccountData) IsEmpty() bool { return bo.IsVotingEmpty() && bo.MicroAlgos.Raw == 0 && - bo.RewardsBase == 0 + bo.RewardsBase == 0 && !bo.IncentiveEligible } // GetOnlineAccount returns ledgercore.OnlineAccount for top online accounts / voters @@ -459,6 +482,7 @@ func (bo *BaseOnlineAccountData) GetOnlineAccountData(proto config.ConsensusPara VoteLastValid: bo.VoteLastValid, VoteKeyDilution: bo.VoteKeyDilution, }, + IncentiveEligible: bo.IncentiveEligible, } } @@ -471,9 +495,10 @@ func (bo *BaseOnlineAccountData) NormalizedOnlineBalance(proto config.ConsensusP func (bo *BaseOnlineAccountData) SetCoreAccountData(ad *ledgercore.AccountData) { bo.BaseVotingData.SetCoreAccountData(ad) - // MicroAlgos/RewardsBase are updated by the evaluator when accounts are touched + // These are updated by the evaluator when accounts are touched bo.MicroAlgos = ad.MicroAlgos bo.RewardsBase = ad.RewardsBase + bo.IncentiveEligible = ad.IncentiveEligible } // MakeResourcesData returns a new empty instance of resourcesData. diff --git a/ledger/store/trackerdb/data_test.go b/ledger/store/trackerdb/data_test.go index e329a84e74..edc0d0dc9e 100644 --- a/ledger/store/trackerdb/data_test.go +++ b/ledger/store/trackerdb/data_test.go @@ -1105,7 +1105,7 @@ func TestBaseAccountDataIsEmpty(t *testing.T) { structureTesting := func(t *testing.T) { encoding, err := json.Marshal(&empty) zeros32 := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" - expectedEncoding := `{"Status":0,"MicroAlgos":{"Raw":0},"RewardsBase":0,"RewardedMicroAlgos":{"Raw":0},"AuthAddr":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ","TotalAppSchemaNumUint":0,"TotalAppSchemaNumByteSlice":0,"TotalExtraAppPages":0,"TotalAssetParams":0,"TotalAssets":0,"TotalAppParams":0,"TotalAppLocalStates":0,"TotalBoxes":0,"TotalBoxBytes":0,"VoteID":[` + zeros32 + `],"SelectionID":[` + zeros32 + `],"VoteFirstValid":0,"VoteLastValid":0,"VoteKeyDilution":0,"StateProofID":[` + zeros32 + `,` + zeros32 + `],"UpdateRound":0}` + expectedEncoding := `{"Status":0,"MicroAlgos":{"Raw":0},"RewardsBase":0,"RewardedMicroAlgos":{"Raw":0},"AuthAddr":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ","TotalAppSchemaNumUint":0,"TotalAppSchemaNumByteSlice":0,"TotalExtraAppPages":0,"TotalAssetParams":0,"TotalAssets":0,"TotalAppParams":0,"TotalAppLocalStates":0,"TotalBoxes":0,"TotalBoxBytes":0,"IncentiveEligible":false,"LastProposed":0,"LastHeartbeat":0,"VoteID":[` + zeros32 + `],"SelectionID":[` + zeros32 + `],"VoteFirstValid":0,"VoteLastValid":0,"VoteKeyDilution":0,"StateProofID":[` + zeros32 + `,` + zeros32 + `],"UpdateRound":0}` require.NoError(t, err) require.Equal(t, expectedEncoding, string(encoding)) } @@ -1152,7 +1152,7 @@ func TestBaseOnlineAccountDataIsEmpty(t *testing.T) { structureTesting := func(t *testing.T) { encoding, err := json.Marshal(&empty) zeros32 := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" - expectedEncoding := `{"VoteID":[` + zeros32 + `],"SelectionID":[` + zeros32 + `],"VoteFirstValid":0,"VoteLastValid":0,"VoteKeyDilution":0,"StateProofID":[` + zeros32 + `,` + zeros32 + `],"MicroAlgos":{"Raw":0},"RewardsBase":0}` + expectedEncoding := `{"VoteID":[` + zeros32 + `],"SelectionID":[` + zeros32 + `],"VoteFirstValid":0,"VoteLastValid":0,"VoteKeyDilution":0,"StateProofID":[` + zeros32 + `,` + zeros32 + `],"IncentiveEligible":false,"MicroAlgos":{"Raw":0},"RewardsBase":0}` require.NoError(t, err) require.Equal(t, expectedEncoding, string(encoding)) } @@ -1249,7 +1249,7 @@ func TestBaseOnlineAccountDataReflect(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - require.Equal(t, 4, reflect.TypeOf(BaseOnlineAccountData{}).NumField(), "update all getters and setters for baseOnlineAccountData and change the field count") + require.Equal(t, 5, reflect.TypeOf(BaseOnlineAccountData{}).NumField(), "update all getters and setters for baseOnlineAccountData and change the field count") } func TestBaseVotingDataReflect(t *testing.T) { diff --git a/ledger/store/trackerdb/msgp_gen.go b/ledger/store/trackerdb/msgp_gen.go index a13469c0ab..465248e93d 100644 --- a/ledger/store/trackerdb/msgp_gen.go +++ b/ledger/store/trackerdb/msgp_gen.go @@ -100,8 +100,8 @@ import ( func (z *BaseAccountData) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(21) - var zb0001Mask uint32 /* 23 bits */ + zb0001Len := uint32(24) + var zb0001Mask uint32 /* 26 bits */ if (*z).BaseVotingData.VoteID.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x1 @@ -182,10 +182,22 @@ func (z *BaseAccountData) MarshalMsg(b []byte) (o []byte) { zb0001Len-- zb0001Mask |= 0x200000 } - if (*z).UpdateRound == 0 { + if (*z).IncentiveEligible == false { zb0001Len-- zb0001Mask |= 0x400000 } + if (*z).LastProposed.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x800000 + } + if (*z).LastHeartbeat.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x1000000 + } + if (*z).UpdateRound == 0 { + zb0001Len-- + zb0001Mask |= 0x2000000 + } // variable map header, size zb0001Len o = msgp.AppendMapHeader(o, zb0001Len) if zb0001Len != 0 { @@ -290,6 +302,21 @@ func (z *BaseAccountData) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).TotalBoxBytes) } if (zb0001Mask & 0x400000) == 0 { // if not empty + // string "o" + o = append(o, 0xa1, 0x6f) + o = msgp.AppendBool(o, (*z).IncentiveEligible) + } + if (zb0001Mask & 0x800000) == 0 { // if not empty + // string "p" + o = append(o, 0xa1, 0x70) + o = (*z).LastProposed.MarshalMsg(o) + } + if (zb0001Mask & 0x1000000) == 0 { // if not empty + // string "q" + o = append(o, 0xa1, 0x71) + o = (*z).LastHeartbeat.MarshalMsg(o) + } + if (zb0001Mask & 0x2000000) == 0 { // if not empty // string "z" o = append(o, 0xa1, 0x7a) o = msgp.AppendUint64(o, (*z).UpdateRound) @@ -433,6 +460,30 @@ func (z *BaseAccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalSta return } } + if zb0001 > 0 { + zb0001-- + (*z).IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "IncentiveEligible") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).LastProposed.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastProposed") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).LastHeartbeat.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastHeartbeat") + return + } + } if zb0001 > 0 { zb0001-- bts, err = (*z).BaseVotingData.VoteID.UnmarshalMsgWithState(bts, st) @@ -596,6 +647,24 @@ func (z *BaseAccountData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalSta err = msgp.WrapError(err, "TotalBoxBytes") return } + case "o": + (*z).IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "IncentiveEligible") + return + } + case "p": + bts, err = (*z).LastProposed.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "LastProposed") + return + } + case "q": + bts, err = (*z).LastHeartbeat.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "LastHeartbeat") + return + } case "A": bts, err = (*z).BaseVotingData.VoteID.UnmarshalMsgWithState(bts, st) if err != nil { @@ -661,18 +730,18 @@ func (_ *BaseAccountData) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *BaseAccountData) Msgsize() (s int) { - s = 3 + 2 + (*z).Status.Msgsize() + 2 + (*z).MicroAlgos.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).RewardedMicroAlgos.Msgsize() + 2 + (*z).AuthAddr.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + (*z).BaseVotingData.VoteID.Msgsize() + 2 + (*z).BaseVotingData.SelectionID.Msgsize() + 2 + (*z).BaseVotingData.VoteFirstValid.Msgsize() + 2 + (*z).BaseVotingData.VoteLastValid.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).BaseVotingData.StateProofID.Msgsize() + 2 + msgp.Uint64Size + s = 3 + 2 + (*z).Status.Msgsize() + 2 + (*z).MicroAlgos.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).RewardedMicroAlgos.Msgsize() + 2 + (*z).AuthAddr.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.BoolSize + 2 + (*z).LastProposed.Msgsize() + 2 + (*z).LastHeartbeat.Msgsize() + 2 + (*z).BaseVotingData.VoteID.Msgsize() + 2 + (*z).BaseVotingData.SelectionID.Msgsize() + 2 + (*z).BaseVotingData.VoteFirstValid.Msgsize() + 2 + (*z).BaseVotingData.VoteLastValid.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).BaseVotingData.StateProofID.Msgsize() + 2 + msgp.Uint64Size return } // MsgIsZero returns whether this is a zero value func (z *BaseAccountData) MsgIsZero() bool { - return ((*z).Status.MsgIsZero()) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) && ((*z).RewardedMicroAlgos.MsgIsZero()) && ((*z).AuthAddr.MsgIsZero()) && ((*z).TotalAppSchemaNumUint == 0) && ((*z).TotalAppSchemaNumByteSlice == 0) && ((*z).TotalExtraAppPages == 0) && ((*z).TotalAssetParams == 0) && ((*z).TotalAssets == 0) && ((*z).TotalAppParams == 0) && ((*z).TotalAppLocalStates == 0) && ((*z).TotalBoxes == 0) && ((*z).TotalBoxBytes == 0) && ((*z).BaseVotingData.VoteID.MsgIsZero()) && ((*z).BaseVotingData.SelectionID.MsgIsZero()) && ((*z).BaseVotingData.VoteFirstValid.MsgIsZero()) && ((*z).BaseVotingData.VoteLastValid.MsgIsZero()) && ((*z).BaseVotingData.VoteKeyDilution == 0) && ((*z).BaseVotingData.StateProofID.MsgIsZero()) && ((*z).UpdateRound == 0) + return ((*z).Status.MsgIsZero()) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) && ((*z).RewardedMicroAlgos.MsgIsZero()) && ((*z).AuthAddr.MsgIsZero()) && ((*z).TotalAppSchemaNumUint == 0) && ((*z).TotalAppSchemaNumByteSlice == 0) && ((*z).TotalExtraAppPages == 0) && ((*z).TotalAssetParams == 0) && ((*z).TotalAssets == 0) && ((*z).TotalAppParams == 0) && ((*z).TotalAppLocalStates == 0) && ((*z).TotalBoxes == 0) && ((*z).TotalBoxBytes == 0) && ((*z).IncentiveEligible == false) && ((*z).LastProposed.MsgIsZero()) && ((*z).LastHeartbeat.MsgIsZero()) && ((*z).BaseVotingData.VoteID.MsgIsZero()) && ((*z).BaseVotingData.SelectionID.MsgIsZero()) && ((*z).BaseVotingData.VoteFirstValid.MsgIsZero()) && ((*z).BaseVotingData.VoteLastValid.MsgIsZero()) && ((*z).BaseVotingData.VoteKeyDilution == 0) && ((*z).BaseVotingData.StateProofID.MsgIsZero()) && ((*z).UpdateRound == 0) } // MaxSize returns a maximum valid message size for this message type func BaseAccountDataMaxSize() (s int) { - s = 3 + 2 + basics.StatusMaxSize() + 2 + basics.MicroAlgosMaxSize() + 2 + msgp.Uint64Size + 2 + basics.MicroAlgosMaxSize() + 2 + basics.AddressMaxSize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + crypto.OneTimeSignatureVerifierMaxSize() + 2 + crypto.VRFVerifierMaxSize() + 2 + basics.RoundMaxSize() + 2 + basics.RoundMaxSize() + 2 + msgp.Uint64Size + 2 + merklesignature.CommitmentMaxSize() + 2 + msgp.Uint64Size + s = 3 + 2 + basics.StatusMaxSize() + 2 + basics.MicroAlgosMaxSize() + 2 + msgp.Uint64Size + 2 + basics.MicroAlgosMaxSize() + 2 + basics.AddressMaxSize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.BoolSize + 2 + basics.RoundMaxSize() + 2 + basics.RoundMaxSize() + 2 + crypto.OneTimeSignatureVerifierMaxSize() + 2 + crypto.VRFVerifierMaxSize() + 2 + basics.RoundMaxSize() + 2 + basics.RoundMaxSize() + 2 + msgp.Uint64Size + 2 + merklesignature.CommitmentMaxSize() + 2 + msgp.Uint64Size return } @@ -680,8 +749,8 @@ func BaseAccountDataMaxSize() (s int) { func (z *BaseOnlineAccountData) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(8) - var zb0001Mask uint16 /* 10 bits */ + zb0001Len := uint32(9) + var zb0001Mask uint16 /* 11 bits */ if (*z).BaseVotingData.VoteID.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x1 @@ -706,14 +775,18 @@ func (z *BaseOnlineAccountData) MarshalMsg(b []byte) (o []byte) { zb0001Len-- zb0001Mask |= 0x20 } - if (*z).MicroAlgos.MsgIsZero() { + if (*z).IncentiveEligible == false { zb0001Len-- zb0001Mask |= 0x40 } - if (*z).RewardsBase == 0 { + if (*z).MicroAlgos.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x80 } + if (*z).RewardsBase == 0 { + zb0001Len-- + zb0001Mask |= 0x100 + } // variable map header, size zb0001Len o = append(o, 0x80|uint8(zb0001Len)) if zb0001Len != 0 { @@ -748,11 +821,16 @@ func (z *BaseOnlineAccountData) MarshalMsg(b []byte) (o []byte) { o = (*z).BaseVotingData.StateProofID.MarshalMsg(o) } if (zb0001Mask & 0x40) == 0 { // if not empty + // string "X" + o = append(o, 0xa1, 0x58) + o = msgp.AppendBool(o, (*z).IncentiveEligible) + } + if (zb0001Mask & 0x80) == 0 { // if not empty // string "Y" o = append(o, 0xa1, 0x59) o = (*z).MicroAlgos.MarshalMsg(o) } - if (zb0001Mask & 0x80) == 0 { // if not empty + if (zb0001Mask & 0x100) == 0 { // if not empty // string "Z" o = append(o, 0xa1, 0x5a) o = msgp.AppendUint64(o, (*z).RewardsBase) @@ -832,6 +910,14 @@ func (z *BaseOnlineAccountData) UnmarshalMsgWithState(bts []byte, st msgp.Unmars return } } + if zb0001 > 0 { + zb0001-- + (*z).IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "IncentiveEligible") + return + } + } if zb0001 > 0 { zb0001-- bts, err = (*z).MicroAlgos.UnmarshalMsgWithState(bts, st) @@ -907,6 +993,12 @@ func (z *BaseOnlineAccountData) UnmarshalMsgWithState(bts []byte, st msgp.Unmars err = msgp.WrapError(err, "StateProofID") return } + case "X": + (*z).IncentiveEligible, bts, err = msgp.ReadBoolBytes(bts) + if err != nil { + err = msgp.WrapError(err, "IncentiveEligible") + return + } case "Y": bts, err = (*z).MicroAlgos.UnmarshalMsgWithState(bts, st) if err != nil { @@ -942,18 +1034,18 @@ func (_ *BaseOnlineAccountData) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *BaseOnlineAccountData) Msgsize() (s int) { - s = 1 + 2 + (*z).BaseVotingData.VoteID.Msgsize() + 2 + (*z).BaseVotingData.SelectionID.Msgsize() + 2 + (*z).BaseVotingData.VoteFirstValid.Msgsize() + 2 + (*z).BaseVotingData.VoteLastValid.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).BaseVotingData.StateProofID.Msgsize() + 2 + (*z).MicroAlgos.Msgsize() + 2 + msgp.Uint64Size + s = 1 + 2 + (*z).BaseVotingData.VoteID.Msgsize() + 2 + (*z).BaseVotingData.SelectionID.Msgsize() + 2 + (*z).BaseVotingData.VoteFirstValid.Msgsize() + 2 + (*z).BaseVotingData.VoteLastValid.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).BaseVotingData.StateProofID.Msgsize() + 2 + msgp.BoolSize + 2 + (*z).MicroAlgos.Msgsize() + 2 + msgp.Uint64Size return } // MsgIsZero returns whether this is a zero value func (z *BaseOnlineAccountData) MsgIsZero() bool { - return ((*z).BaseVotingData.VoteID.MsgIsZero()) && ((*z).BaseVotingData.SelectionID.MsgIsZero()) && ((*z).BaseVotingData.VoteFirstValid.MsgIsZero()) && ((*z).BaseVotingData.VoteLastValid.MsgIsZero()) && ((*z).BaseVotingData.VoteKeyDilution == 0) && ((*z).BaseVotingData.StateProofID.MsgIsZero()) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) + return ((*z).BaseVotingData.VoteID.MsgIsZero()) && ((*z).BaseVotingData.SelectionID.MsgIsZero()) && ((*z).BaseVotingData.VoteFirstValid.MsgIsZero()) && ((*z).BaseVotingData.VoteLastValid.MsgIsZero()) && ((*z).BaseVotingData.VoteKeyDilution == 0) && ((*z).BaseVotingData.StateProofID.MsgIsZero()) && ((*z).IncentiveEligible == false) && ((*z).MicroAlgos.MsgIsZero()) && ((*z).RewardsBase == 0) } // MaxSize returns a maximum valid message size for this message type func BaseOnlineAccountDataMaxSize() (s int) { - s = 1 + 2 + crypto.OneTimeSignatureVerifierMaxSize() + 2 + crypto.VRFVerifierMaxSize() + 2 + basics.RoundMaxSize() + 2 + basics.RoundMaxSize() + 2 + msgp.Uint64Size + 2 + merklesignature.CommitmentMaxSize() + 2 + basics.MicroAlgosMaxSize() + 2 + msgp.Uint64Size + s = 1 + 2 + crypto.OneTimeSignatureVerifierMaxSize() + 2 + crypto.VRFVerifierMaxSize() + 2 + basics.RoundMaxSize() + 2 + basics.RoundMaxSize() + 2 + msgp.Uint64Size + 2 + merklesignature.CommitmentMaxSize() + 2 + msgp.BoolSize + 2 + basics.MicroAlgosMaxSize() + 2 + msgp.Uint64Size return } diff --git a/ledger/store/trackerdb/sqlitedriver/schema.go b/ledger/store/trackerdb/sqlitedriver/schema.go index 369ff264e0..a36dd2cd90 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema.go +++ b/ledger/store/trackerdb/sqlitedriver/schema.go @@ -734,8 +734,12 @@ func performOnlineAccountsTableMigration(ctx context.Context, e db.Executable, p } } - // remove stateproofID field for offline accounts - if ba.Status != basics.Online && !ba.StateProofID.IsEmpty() { + // We had a bug that didn't remove StateProofIDs when going offline. + // Tidy up such accounts. We don't zero it out based on + // `!basics.Online` because accounts can be suspended, in which case + // they are Offline, but retain their voting material. But it remains + // illegal to have a StateProofID without a SelectionID. + if ba.SelectionID.IsEmpty() && !ba.StateProofID.IsEmpty() { // store old data for account hash update state := acctState{old: ba, oldEnc: encodedAcctData} ba.StateProofID = merklesignature.Commitment{} diff --git a/ledger/store/trackerdb/sqlitedriver/schema_test.go b/ledger/store/trackerdb/sqlitedriver/schema_test.go index e7aee0b2a9..9143eba9c1 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema_test.go +++ b/ledger/store/trackerdb/sqlitedriver/schema_test.go @@ -166,7 +166,7 @@ func TestAccountDBTxTailLoad(t *testing.T) { } } -func TestRemoveOfflineStateProofID(t *testing.T) { +func TestRemoveStrayStateProofID(t *testing.T) { partitiontest.PartitionTest(t) accts := ledgertesting.RandomAccounts(20, true) @@ -176,11 +176,10 @@ func TestRemoveOfflineStateProofID(t *testing.T) { accts[addr] = acct expectedAcct := acct - if acct.Status != basics.Online { + if acct.SelectionID.IsEmpty() { expectedAcct.StateProofID = merklesignature.Commitment{} } expectedAccts[addr] = expectedAcct - } buildDB := func(accounts map[basics.Address]basics.AccountData) (db.Pair, *sql.Tx) { @@ -211,7 +210,7 @@ func TestRemoveOfflineStateProofID(t *testing.T) { defer dbs.Close() defer tx.Rollback() - // make second copy of DB to prepare exepected/fixed merkle trie + // make second copy of DB to prepare expected/fixed merkle trie expectedDBs, expectedTx := buildDB(expectedAccts) defer expectedDBs.Close() defer expectedTx.Rollback() @@ -237,8 +236,8 @@ func TestRemoveOfflineStateProofID(t *testing.T) { var ba trackerdb.BaseAccountData err = protocol.Decode(encodedAcctData, &ba) require.NoError(t, err) - if expected && ba.Status != basics.Online { - require.Equal(t, merklesignature.Commitment{}, ba.StateProofID) + if expected && ba.SelectionID.IsEmpty() { + require.Zero(t, ba.StateProofID) } addHash := trackerdb.AccountHashBuilderV6(addr, &ba, encodedAcctData) added, err := trie.Add(addHash) @@ -287,8 +286,8 @@ func TestRemoveOfflineStateProofID(t *testing.T) { var ba trackerdb.BaseAccountData err = protocol.Decode(encodedAcctData, &ba) require.NoError(t, err) - if ba.Status != basics.Online { - require.True(t, ba.StateProofID.IsEmpty()) + if ba.SelectionID.IsEmpty() { + require.Zero(t, ba.StateProofID) } } } diff --git a/ledger/testing/randomAccounts.go b/ledger/testing/randomAccounts.go index 2d5f79c82a..d97eee20d4 100644 --- a/ledger/testing/randomAccounts.go +++ b/ledger/testing/randomAccounts.go @@ -18,11 +18,9 @@ package testing import ( "fmt" - "math" "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/basics" "github.com/algorand/go-algorand/protocol" @@ -58,47 +56,62 @@ func RandomNote() []byte { return note[:] } -// RandomAccountData generates a random AccountData +// RandomAccountData generates a random AccountData with no associated resources. func RandomAccountData(rewardsBase uint64) basics.AccountData { var data basics.AccountData // Avoid overflowing totals data.MicroAlgos.Raw = crypto.RandUint64() % (1 << 32) + // 0 is an invalid round, but would be right if never proposed + data.LastProposed = basics.Round(crypto.RandUint64() % 10) + // 0 is an invalid round, but would be right if never needed a heartbeat + data.LastHeartbeat = basics.Round(crypto.RandUint64() % 10) switch crypto.RandUint64() % 3 { case 0: data.Status = basics.Online - data.VoteLastValid = 10000 + data.IncentiveEligible = crypto.RandUint64()%5 == 0 case 1: data.Status = basics.Offline - data.VoteLastValid = 0 - default: + case 2: data.Status = basics.NotParticipating } - data.VoteFirstValid = 0 + // Give online accounts voting data, and some of the offline too. They are "suspended". + if data.Status == basics.Online || (data.Status == basics.Offline && crypto.RandUint64()%5 == 1) { + crypto.RandBytes(data.VoteID[:]) + crypto.RandBytes(data.SelectionID[:]) + crypto.RandBytes(data.StateProofID[:]) + data.VoteFirstValid = basics.Round(crypto.RandUint64()) + data.VoteLastValid = basics.Round(crypto.RandUint63()) // int64 is the max sqlite can store + data.VoteKeyDilution = crypto.RandUint64() + } + data.RewardsBase = rewardsBase return data } // RandomOnlineAccountData is similar to RandomAccountData but always creates online account func RandomOnlineAccountData(rewardsBase uint64) basics.AccountData { - var data basics.AccountData - data.MicroAlgos.Raw = crypto.RandUint64() % (1 << 32) - data.Status = basics.Online - data.VoteLastValid = 1000 - data.VoteFirstValid = 0 - data.RewardsBase = rewardsBase - return data + for { + data := RandomAccountData(rewardsBase) + if data.Status == basics.Online { + return data + } + } } -// RandomAssetParams creates a randim basics.AssetParams +// RandomAssetParams creates a random basics.AssetParams func RandomAssetParams() basics.AssetParams { ap := basics.AssetParams{ Total: crypto.RandUint64(), Decimals: uint32(crypto.RandUint64() % 20), DefaultFrozen: crypto.RandUint64()%2 == 0, } + // Since 0 and 1 Total assets seem extra interesting, make them more often. + if crypto.RandUint64()%5 != 0 { + ap.Total = crypto.RandUint64() % 2 + } if crypto.RandUint64()%5 != 0 { ap.UnitName = fmt.Sprintf("un%x", uint32(crypto.RandUint64()%0x7fffff)) } @@ -214,6 +227,7 @@ func RandomAppParams() basics.AppParams { if len(ap.GlobalState) == 0 { ap.GlobalState = nil } + ap.ExtraProgramPages = uint32(crypto.RandUint64() % 4) return ap } @@ -268,21 +282,6 @@ func RandomAppLocalState() basics.AppLocalState { func RandomFullAccountData(rewardsLevel uint64, lastCreatableID *basics.CreatableIndex, assets map[basics.AssetIndex]struct{}, apps map[basics.AppIndex]struct{}) basics.AccountData { data := RandomAccountData(rewardsLevel) - if data.Status == basics.Online { - crypto.RandBytes(data.VoteID[:]) - crypto.RandBytes(data.SelectionID[:]) - crypto.RandBytes(data.StateProofID[:]) - data.VoteFirstValid = basics.Round(crypto.RandUint64()) - data.VoteLastValid = basics.Round(crypto.RandUint64() % uint64(math.MaxInt64)) // int64 is the max sqlite can store - data.VoteKeyDilution = crypto.RandUint64() - } else { - data.VoteID = crypto.OneTimeSignatureVerifier{} - data.SelectionID = crypto.VRFVerifier{} - data.StateProofID = merklesignature.Commitment{} - data.VoteFirstValid = 0 - data.VoteLastValid = 0 - data.VoteKeyDilution = 0 - } if (crypto.RandUint64() % 2) == 1 { // if account has created assets, have these defined. createdAssetsCount := crypto.RandUint64()%20 + 1 diff --git a/ledger/testing/randomAccounts_test.go b/ledger/testing/randomAccounts_test.go index 9f69321aaa..f59f704129 100644 --- a/ledger/testing/randomAccounts_test.go +++ b/ledger/testing/randomAccounts_test.go @@ -87,7 +87,6 @@ func TestAccounts(t *testing.T) { zeroValueExceptions := []reflectionhelpers.TypePath{ reflectionhelpers.TypePath{}.AddField("MicroAlgos").AddField("Raw"), reflectionhelpers.TypePath{}.AddField("AssetParams").AddMapKey(), - reflectionhelpers.TypePath{}.AddField("AssetParams").AddValue().AddField("Total"), reflectionhelpers.TypePath{}.AddField("Assets").AddMapKey(), reflectionhelpers.TypePath{}.AddField("AppLocalStates").AddMapKey(), reflectionhelpers.TypePath{}.AddField("AppLocalStates").AddValue().AddField("KeyValue").AddValue().AddField("Type"), diff --git a/ledger/testing/testGenesis.go b/ledger/testing/testGenesis.go index a2d469dcf0..81be9e9118 100644 --- a/ledger/testing/testGenesis.go +++ b/ledger/testing/testGenesis.go @@ -25,25 +25,25 @@ import ( "github.com/algorand/go-algorand/protocol" ) -// testGenesisCfg provides a configuration object for NewTestGenesis. -type testGenesisCfg struct { +// GenesisCfg provides a configuration object for NewTestGenesis. +type GenesisCfg struct { rewardsPoolAmount basics.MicroAlgos + OnlineCount int } // TestGenesisOption provides functional options for testGenesisCfg. -type TestGenesisOption func(*testGenesisCfg) +type TestGenesisOption func(*GenesisCfg) -// TestGenesisRewardsPoolSize configures the rewards pool size in the genesis block. -func TestGenesisRewardsPoolSize(amount basics.MicroAlgos) TestGenesisOption { - return func(cfg *testGenesisCfg) { cfg.rewardsPoolAmount = amount } -} +// TurnOffRewards turns off the rewards pool for tests that are sensitive to +// "surprise" balance changes. +var TurnOffRewards = func(cfg *GenesisCfg) { cfg.rewardsPoolAmount = basics.MicroAlgos{Raw: 100_000} } // NewTestGenesis creates a bunch of accounts, splits up 10B algos // between them and the rewardspool and feesink, and gives out the // addresses and secrets it creates to enable tests. For special // scenarios, manipulate these return values before using newTestLedger. func NewTestGenesis(opts ...TestGenesisOption) (bookkeeping.GenesisBalances, []basics.Address, []*crypto.SignatureSecrets) { - var cfg testGenesisCfg + var cfg GenesisCfg for _, opt := range opts { opt(&cfg) } @@ -75,6 +75,15 @@ func NewTestGenesis(opts ...TestGenesisOption) (bookkeeping.GenesisBalances, []b adata := basics.AccountData{ MicroAlgos: basics.MicroAlgos{Raw: amount}, + Status: basics.Offline, + } + if i < cfg.OnlineCount { + adata.Status = basics.Online + adata.VoteFirstValid = 0 + adata.VoteLastValid = 1_000_000 + crypto.RandBytes(adata.VoteID[:]) + crypto.RandBytes(adata.SelectionID[:]) + crypto.RandBytes(adata.StateProofID[:]) } accts[addrs[i]] = adata } diff --git a/node/assemble_test.go b/node/assemble_test.go index 6954d99400..51ff7d8edc 100644 --- a/node/assemble_test.go +++ b/node/assemble_test.go @@ -99,7 +99,7 @@ func BenchmarkAssembleBlock(b *testing.B) { cfg := config.GetDefaultLocal() cfg.TxPoolSize = txPoolSize cfg.EnableAssembleStats = false - tp := pools.MakeTransactionPool(l.Ledger, cfg, logging.Base()) + tp := pools.MakeTransactionPool(l.Ledger, cfg, logging.Base(), nil) errcount := 0 okcount := 0 var worstTxID transactions.Txid @@ -220,13 +220,13 @@ func TestAssembleBlockTransactionPoolBehind(t *testing.T) { cfg = config.GetDefaultLocal() cfg.TxPoolSize = txPoolSize cfg.EnableAssembleStats = false - tp := pools.MakeTransactionPool(l.Ledger, cfg, log) + tp := pools.MakeTransactionPool(l.Ledger, cfg, log, nil) next := l.NextRound() deadline := time.Now().Add(time.Second) block, err := tp.AssembleBlock(next, deadline) require.NoError(t, err) - require.NoError(t, ledger.AddBlock(block.Block(), agreement.Certificate{Round: next})) + require.NoError(t, ledger.AddBlock(block.UnfinishedBlock(), agreement.Certificate{Round: next})) expectingLog = true @@ -234,7 +234,7 @@ func TestAssembleBlockTransactionPoolBehind(t *testing.T) { deadline = time.Now().Add(time.Second) block, err = tp.AssembleBlock(next, deadline) require.NoError(t, err) - require.NoError(t, ledger.AddBlock(block.Block(), agreement.Certificate{Round: next})) + require.NoError(t, ledger.AddBlock(block.UnfinishedBlock(), agreement.Certificate{Round: next})) require.False(t, expectingLog) } diff --git a/node/impls.go b/node/impls.go index b7ee54fab7..826f0399c4 100644 --- a/node/impls.go +++ b/node/impls.go @@ -26,6 +26,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/ledger" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/util/execpool" @@ -59,7 +60,7 @@ func (i blockValidatorImpl) Validate(ctx context.Context, e bookkeeping.Block) ( return nil, err } - return validatedBlock{vb: lvb}, nil + return lvb, nil } // agreementLedger implements the agreement.Ledger interface. @@ -86,7 +87,7 @@ func (l agreementLedger) EnsureBlock(e bookkeeping.Block, c agreement.Certificat // EnsureValidatedBlock implements agreement.LedgerWriter.EnsureValidatedBlock. func (l agreementLedger) EnsureValidatedBlock(ve agreement.ValidatedBlock, c agreement.Certificate) { - l.Ledger.EnsureValidatedBlock(ve.(validatedBlock).vb, c) + l.Ledger.EnsureValidatedBlock(ve.(*ledgercore.ValidatedBlock), c) // let the network know that we've made some progress. l.n.OnNetworkAdvance() } diff --git a/node/node.go b/node/node.go index 53c6c492a9..88766877dc 100644 --- a/node/node.go +++ b/node/node.go @@ -228,7 +228,22 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd return nil, err } - node.transactionPool = pools.MakeTransactionPool(node.ledger.Ledger, cfg, 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 + } + node.accountManager = data.MakeAccountManager(log, registry) + + err = node.loadParticipationKeys() + if err != nil { + log.Errorf("Cannot load participation keys: %v", err) + return nil, err + } + + node.oldKeyDeletionNotify = make(chan struct{}, 1) + + node.transactionPool = pools.MakeTransactionPool(node.ledger.Ledger, cfg, node.log, node) blockListeners := []ledgercore.BlockListener{ node.transactionPool, @@ -300,21 +315,6 @@ 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(node.genesisDirs.ColdGenesisDir, node.log) - if err != nil { - log.Errorf("unable to initialize the participation registry database: %v", err) - return nil, err - } - node.accountManager = data.MakeAccountManager(log, registry) - - err = node.loadParticipationKeys() - if err != nil { - log.Errorf("Cannot load participation keys: %v", err) - return nil, err - } - - node.oldKeyDeletionNotify = make(chan struct{}, 1) - catchpointCatchupState, err := node.ledger.GetCatchpointCatchupState(context.Background()) if err != nil { log.Errorf("unable to determine catchpoint catchup state: %v", err) @@ -455,20 +455,20 @@ func (node *AlgorandFullNode) Ledger() *data.Ledger { // writeDevmodeBlock generates a new block for a devmode, and write it to the ledger. func (node *AlgorandFullNode) writeDevmodeBlock() (err error) { - var vb *ledgercore.ValidatedBlock + var vb *ledgercore.UnfinishedBlock vb, err = node.transactionPool.AssembleDevModeBlock() if err != nil || vb == nil { return } - // Make a new validated block. - prevRound := vb.Block().Round() - 1 + // Make a new validated block from this UnfinishedBlock. + prevRound := vb.Round() - 1 prev, err := node.ledger.BlockHdr(prevRound) if err != nil { return err } - blk := vb.Block() + blk := vb.UnfinishedBlock() // Set block timestamp based on offset, if set. // Make sure block timestamp is not greater than MaxInt64. @@ -476,11 +476,10 @@ func (node *AlgorandFullNode) writeDevmodeBlock() (err error) { blk.TimeStamp = prev.TimeStamp + *node.timestampOffset } blk.BlockHeader.Seed = committee.Seed(prev.Hash()) - vb2 := ledgercore.MakeValidatedBlock(blk, vb.Delta()) - vb = &vb2 + vb2 := ledgercore.MakeValidatedBlock(blk, vb.UnfinishedDeltas()) // add the newly generated block to the ledger - err = node.ledger.AddValidatedBlock(*vb, agreement.Certificate{Round: vb.Block().Round()}) + err = node.ledger.AddValidatedBlock(vb2, agreement.Certificate{Round: vb2.Block().Round()}) return err } @@ -1290,27 +1289,23 @@ func (node *AlgorandFullNode) SetCatchpointCatchupMode(catchpointCatchupMode boo } -// validatedBlock satisfies agreement.ValidatedBlock -type validatedBlock struct { - vb *ledgercore.ValidatedBlock +// unfinishedBlock satisfies agreement.UnfinishedBlock +type unfinishedBlock struct { + blk *ledgercore.UnfinishedBlock } -// WithSeed satisfies the agreement.ValidatedBlock interface. -func (vb validatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { - lvb := vb.vb.WithSeed(s) - return validatedBlock{vb: &lvb} -} +// Round satisfies the agreement.UnfinishedBlock interface. +func (ub unfinishedBlock) Round() basics.Round { return ub.blk.Round() } -// Block satisfies the agreement.ValidatedBlock interface. -func (vb validatedBlock) Block() bookkeeping.Block { - blk := vb.vb.Block() - return blk +// FinishBlock satisfies the agreement.UnfinishedBlock interface. +func (ub unfinishedBlock) FinishBlock(s committee.Seed, proposer basics.Address, eligible bool) agreement.Block { + return agreement.Block(ub.blk.FinishBlock(s, proposer, eligible)) } // AssembleBlock implements Ledger.AssembleBlock. -func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (agreement.ValidatedBlock, error) { +func (node *AlgorandFullNode) AssembleBlock(round basics.Round, addrs []basics.Address) (agreement.UnfinishedBlock, error) { deadline := time.Now().Add(node.config.ProposalAssemblyTime) - lvb, err := node.transactionPool.AssembleBlock(round, deadline) + ub, err := node.transactionPool.AssembleBlock(round, deadline) if err != nil { if errors.Is(err, pools.ErrStaleBlockAssemblyRequest) { // convert specific error to one that would have special handling in the agreement code. @@ -1329,7 +1324,17 @@ func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (agreement.Valid } return nil, err } - return validatedBlock{vb: lvb}, nil + + // ensure UnfinishedBlock contains provided addresses + for _, addr := range addrs { + if !ub.ContainsAddress(addr) { + // this should not happen: VotingKeys() and VotingAccountsForRound() should be in sync + node.log.Errorf("AlgorandFullNode.AssembleBlock: could not generate a proposal for round %d, proposer %s not in UnfinishedBlock", round, addr) + return nil, agreement.ErrAssembleBlockRoundStale + } + } + + return unfinishedBlock{blk: ub}, nil } // getOfflineClosedStatus will return an int with the appropriate bit(s) set if it is offline and/or online @@ -1349,6 +1354,20 @@ func getOfflineClosedStatus(acctData basics.OnlineAccountData) int { return rval } +// VotingAccountsForRound provides a list of addresses that have participation keys valid for the given round. +// These accounts may not all be eligible to propose, but they are a superset of eligible proposers. +func (node *AlgorandFullNode) VotingAccountsForRound(round basics.Round) []basics.Address { + if node.devMode { + return []basics.Address{} + } + parts := node.accountManager.Keys(round) + accounts := make([]basics.Address, len(parts)) + for i, p := range parts { + accounts[i] = p.Account + } + return accounts +} + // VotingKeys implements the key manager's VotingKeys method, and provides additional validation with the ledger. // that allows us to load multiple overlapping keys for the same account, and filter these per-round basis. func (node *AlgorandFullNode) VotingKeys(votingRound, keysRound basics.Round) []account.ParticipationRecordForRound { diff --git a/protocol/tags.go b/protocol/tags.go index e980454674..6cfcacd714 100644 --- a/protocol/tags.go +++ b/protocol/tags.go @@ -73,7 +73,7 @@ const PingReplyTagMaxSize = 8 // ProposalPayloadTagMaxSize is the maximum size of a ProposalPayloadTag message // This value is dominated by the MaxTxnBytesPerBlock -const ProposalPayloadTagMaxSize = 5247980 +const ProposalPayloadTagMaxSize = 5250313 // StateProofSigTagMaxSize is the maximum size of a StateProofSigTag message const StateProofSigTagMaxSize = 6378 diff --git a/test/e2e-go/cli/goal/clerk_test.go b/test/e2e-go/cli/goal/clerk_test.go index ff02474210..05c1495d37 100644 --- a/test/e2e-go/cli/goal/clerk_test.go +++ b/test/e2e-go/cli/goal/clerk_test.go @@ -68,14 +68,14 @@ func TestClerkSendNoteEncoding(t *testing.T) { for i := uint64(0); i < maxRetry && (!foundTx1 || !foundTx2); i++ { if !foundTx1 { - tx1, err := fixture.WaitForConfirmedTxn(status.LastRound+i, account, txID) + tx1, err := fixture.WaitForConfirmedTxn(status.LastRound+i, txID) if err == nil { foundTx1 = true a.Equal(noteText, string(tx1.Txn.Txn.Note)) } } if !foundTx2 { - tx2, err := fixture.WaitForConfirmedTxn(status.LastRound+i, account, txID2) + tx2, err := fixture.WaitForConfirmedTxn(status.LastRound+i, txID2) if err == nil { foundTx2 = true // If the note matches our original text, then goal is still expecting strings encoded diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index 7d731e9e94..3a1eefedc4 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -537,7 +537,7 @@ func TestNodeTxHandlerRestart(t *testing.T) { a.NoError(err) status, err := client1.Status() a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, addrs1[0], tx.ID().String()) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, tx.ID().String()) a.NoError(err) targetCatchpointRound := status.LastRound @@ -563,7 +563,7 @@ func TestNodeTxHandlerRestart(t *testing.T) { status, err = client2.Status() a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(status.LastRound+50, addrs2[0], tx.ID().String()) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+50, tx.ID().String()) a.NoError(err) } @@ -645,7 +645,7 @@ func TestReadyEndpoint(t *testing.T) { a.NoError(err) status, err := client1.Status() a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, addrs1[0], tx.ID().String()) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, tx.ID().String()) a.NoError(err) targetCatchpointRound := status.LastRound @@ -784,7 +784,7 @@ func TestNodeTxSyncRestart(t *testing.T) { a.NoError(err) status, err := client1.Status() a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, addrs1[0], tx.ID().String()) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, tx.ID().String()) a.NoError(err) targetCatchpointRound := status.LastRound @@ -806,7 +806,7 @@ func TestNodeTxSyncRestart(t *testing.T) { a.NoError(err) // the transaction should not be confirmed yet - _, err = fixture.WaitForConfirmedTxn(0, addrs2[0], tx.ID().String()) + _, err = fixture.WaitForConfirmedTxn(0, tx.ID().String()) a.Error(err) // Wait for the catchup @@ -826,6 +826,6 @@ func TestNodeTxSyncRestart(t *testing.T) { status, err = client2.Status() a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(status.LastRound+50, addrs2[0], tx.ID().String()) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+50, tx.ID().String()) a.NoError(err) } diff --git a/test/e2e-go/features/incentives/payouts_test.go b/test/e2e-go/features/incentives/payouts_test.go new file mode 100644 index 0000000000..28fc91089e --- /dev/null +++ b/test/e2e-go/features/incentives/payouts_test.go @@ -0,0 +1,335 @@ +// Copyright (C) 2019-2024 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 suspension + +import ( + "fmt" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "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/bookkeeping" + "github.com/algorand/go-algorand/data/transactions" + "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" +) + +// first bonus payout, set in config/consensus.go +const bonus1 = 10_000_000 + +// TestBasicPayouts shows proposers getting paid +func TestBasicPayouts(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + t.Parallel() + a := require.New(fixtures.SynchronizedTest(t)) + + var fixture fixtures.RestClientFixture + // Make the seed lookback shorter, otherwise we need to wait 320 rounds to become IncentiveEligible. + const lookback = 32 + fixture.FasterConsensus(protocol.ConsensusFuture, time.Second/2, 32) + fmt.Printf("lookback is %d\n", lookback) + fixture.Setup(t, filepath.Join("nettemplates", "Payouts.json")) + defer fixture.Shutdown() + + // Overview of this test: + // rereg to become eligible (must pay extra fee) + // show payouts are paid (from fees and bonuses) + // deplete feesink to ensure it's graceful + + clientAndAccount := func(name string) (libgoal.Client, model.Account) { + c := fixture.GetLibGoalClientForNamedNode(name) + accounts, err := fixture.GetNodeWalletsSortedByBalance(c) + a.NoError(err) + a.Len(accounts, 1) + fmt.Printf("Client %s is %v\n", name, accounts[0].Address) + return c, accounts[0] + } + + c15, account15 := clientAndAccount("Node15") + c01, account01 := clientAndAccount("Node01") + relay, _ := clientAndAccount("Relay") + + data01 := rekeyreg(&fixture, a, c01, account01.Address) + data15 := rekeyreg(&fixture, a, c15, account15.Address) + + // have account01 burn some money to get below the eligibility cap + // Starts with 100M, so burn 60M and get under 70M cap. + txn, err := c01.SendPaymentFromUnencryptedWallet(account01.Address, basics.Address{}.String(), + 1000, 60_000_000_000_000, nil) + a.NoError(err) + burn, err := fixture.WaitForConfirmedTxn(uint64(txn.LastValid), txn.ID().String()) + a.NoError(err) + data01, err = c01.AccountData(account01.Address) + a.NoError(err) + + // Go 31 rounds after the burn happened. During this time, incentive + // eligibility is not in effect yet, so regardless of who proposes, they + // won't earn anything. + client := fixture.LibGoalClient + status, err := client.Status() + a.NoError(err) + for status.LastRound < *burn.ConfirmedRound+lookback-1 { + block, err := client.BookkeepingBlock(status.LastRound) + a.NoError(err) + + fmt.Printf("block %d proposed by %v\n", status.LastRound, block.Proposer()) + a.Zero(block.ProposerPayout()) // nobody is eligible yet (hasn't worked back to balance round) + a.EqualValues(bonus1, block.Bonus.Raw) + + // all nodes agree the proposer proposed. The paranoia here is + // justified. Block incentives are computed in two stages. A little bit + // of extra work is done when agreement "Finishes" the block. An easy + // bug to have is using the block the Deltas() computed on the block + // without the changes that come after agreement runs. We had such an + // optimization, and it would cause failures here. Interface changes + // made since they should make such a problem impossible, but... + for i, c := range []libgoal.Client{c15, c01, relay} { + fmt.Printf("checking block %v\n", block.Round()) + bb, err := getblock(c, status.LastRound) + a.NoError(err) + a.Equal(block.Proposer(), bb.Proposer()) + + // check that the LastProposed for the proposer has been incremented + data, err := c.AccountData(block.Proposer().String()) + a.NoError(err) + // We use LOE instead of Equal because it's possible that by now + // the proposer has proposed again! + a.LessOrEqual(block.Round(), data.LastProposed, "client %d thinks %v", i, block.Proposer()) + } + + next, err := client.AccountData(block.Proposer().String()) + a.LessOrEqual(int(status.LastRound), int(next.LastProposed)) + // regardless of proposer, nobody gets paid + switch block.Proposer().String() { + case account01.Address: + a.Equal(data01.MicroAlgos, next.MicroAlgos) + data01 = next + case account15.Address: + a.Equal(data15.MicroAlgos, next.MicroAlgos) + data15 = next + default: + a.Fail("bad proposer", "%v proposed", block.Proposer) + } + fixture.WaitForRoundWithTimeout(status.LastRound + 1) + status, err = client.Status() + a.NoError(err) + } + + // Wait until each have proposed, so we can see that 01 gets paid and 15 does not (too much balance) + proposed01 := false + proposed15 := false + for i := 0; !proposed01 || !proposed15; i++ { + status, err := client.Status() + a.NoError(err) + block, err := client.BookkeepingBlock(status.LastRound) + a.NoError(err) + a.EqualValues(bonus1, block.Bonus.Raw) + + next, err := client.AccountData(block.Proposer().String()) + fmt.Printf(" proposer %v has %d after proposing round %d\n", block.Proposer(), next.MicroAlgos.Raw, status.LastRound) + + // all nodes agree the proposer proposed + for i, c := range []libgoal.Client{c15, c01, relay} { + data, err := c.AccountData(block.Proposer().String()) + a.NoError(err) + a.Equal(block.Round(), data.LastProposed, i) + } + + // 01 would get paid (because under balance cap) 15 would not + switch block.Proposer().String() { + case account01.Address: + a.EqualValues(bonus1, block.ProposerPayout().Raw) + a.EqualValues(data01.MicroAlgos.Raw+bonus1, next.MicroAlgos.Raw) // 01 earns + proposed01 = true + data01 = next + case account15.Address: + a.Zero(block.ProposerPayout()) + a.Equal(data15.MicroAlgos, next.MicroAlgos) // didn't earn + data15 = next + proposed15 = true + default: + a.Fail("bad proposer", "%v proposed", block.Proposer) + } + fixture.WaitForRoundWithTimeout(status.LastRound + 1) + } + + // Now that we've proven incentives get paid, let's drain the FeeSink and + // ensure it happens gracefully. Have account15 go offline so that (after + // 32 rounds) only account01 (who is eligible) is proposing, so drainage + // will happen soon after. + + offline, err := c15.MakeUnsignedGoOfflineTx(account15.Address, 0, 0, 1000, [32]byte{}) + a.NoError(err) + wh, err := c15.GetUnencryptedWalletHandle() + a.NoError(err) + offlineTxID, err := c15.SignAndBroadcastTransaction(wh, nil, offline) + a.NoError(err) + offTxn, err := fixture.WaitForConfirmedTxn(uint64(offline.LastValid), offlineTxID) + a.NoError(err) + + fmt.Printf(" c15 (%s) will be truly offline (not proposing) after round %d\n", account15.Address, *offTxn.ConfirmedRound+lookback) + + var feesink basics.Address + for i := 0; i < 100; i++ { + status, err := client.Status() + a.NoError(err) + block, err := client.BookkeepingBlock(status.LastRound) + a.NoError(err) + + a.EqualValues(bonus1, block.Bonus.Raw) + + data, err := client.AccountData(block.Proposer().String()) + a.NoError(err) + fmt.Printf(" proposer %v has %d after proposing round %d\n", block.Proposer(), data.MicroAlgos.Raw, status.LastRound) + + pdata, err := c15.AccountData(block.Proposer().String()) + a.NoError(err) + feesink = block.BlockHeader.FeeSink + fdata, err := c15.AccountData(feesink.String()) + a.NoError(err) + + for _, c := range []libgoal.Client{c15, c01, relay} { + data, err = c.AccountData(block.Proposer().String()) + a.NoError(err) + a.Equal(block.Round(), data.LastProposed) + a.Equal(pdata, data) + + data, err = c.AccountData(feesink.String()) + a.NoError(err) + a.Equal(fdata, data) + } + a.LessOrEqual(100000, int(data.MicroAlgos.Raw)) // won't go below minfee + if data.MicroAlgos.Raw == 100000 { + break + } + a.Less(i, int(lookback+20)) + err = fixture.WaitForRoundWithTimeout(status.LastRound + 1) + a.NoError(err) + } + // maybe it got drained before c15 stops proposing. wait. + err = fixture.WaitForRoundWithTimeout(*offTxn.ConfirmedRound + lookback) + a.NoError(err) + + // put 50 algos back into the feesink, show it pays out again + txn, err = c01.SendPaymentFromUnencryptedWallet(account01.Address, feesink.String(), 1000, 50_000_000, nil) + a.NoError(err) + refill, err := fixture.WaitForConfirmedTxn(uint64(txn.LastValid), txn.ID().String()) + fmt.Printf("refilled fee sink in %d\n", *refill.ConfirmedRound) + a.NoError(err) + block, err := client.BookkeepingBlock(*refill.ConfirmedRound) + a.NoError(err) + // 01 is the only one online, so it proposed the block + require.Equal(t, account01.Address, block.Proposer().String()) + // and therefore feesink is already down to ~40 + data, err := relay.AccountData(feesink.String()) + a.NoError(err) + a.Less(int(data.MicroAlgos.Raw), 41_000_000) + a.Greater(int(data.MicroAlgos.Raw), 39_000_000) + + // Closeout c01. This is pretty weird, it means nobody will be online. But + // that will take `lookback` rounds. We will stop the test before then, we just + // want to show that c01 does not get paid if it has closed. + wh, err = c01.GetUnencryptedWalletHandle() + a.NoError(err) + junk := basics.Address{0x01, 0x01}.String() + txn, err = c01.SendPaymentFromWallet(wh, nil, account01.Address, junk, 1000, 0, nil, junk /* close to */, 0, 0) + a.NoError(err) + close, err := fixture.WaitForConfirmedTxn(uint64(txn.LastValid), txn.ID().String()) + a.NoError(err) + fmt.Printf("closed c01 in %d\n", *close.ConfirmedRound) + block, err = client.BookkeepingBlock(*close.ConfirmedRound) + a.NoError(err) + // 01 is the only one online, so it proposed the block + require.Equal(t, account01.Address, block.Proposer().String()) + + // The feesink got was 0.1A, and got 50A in refill.ConfirmedRound. c01 + // closed out in close.ConfirmedRound. So the feesink should have about: + expected := 100_000 + 1_000_000*(50-10*(*close.ConfirmedRound-*refill.ConfirmedRound)) + + // account is gone anyway (it didn't get paid) + data, err = relay.AccountData(account01.Address) + a.Zero(data, "%+v", data) + + data, err = relay.AccountData(feesink.String()) + a.NoError(err) + // Don't want to bother dealing with the exact fees paid in/out. + a.Less(data.MicroAlgos.Raw, expected+5000) + a.Greater(data.MicroAlgos.Raw, expected-5000) + + // Lest one be concerned about that cavalier attitude, wait for a few more + // rounds, and show feesink is unchanged. + a.NoError(fixture.WaitForRoundWithTimeout(*close.ConfirmedRound + 5)) + after, err := relay.AccountData(feesink.String()) + a.NoError(err) + a.Equal(data.MicroAlgos, after.MicroAlgos) +} + +// getblock waits for the given block because we use when we might be talking to +// a client that is behind the network (since it has low stake) +func getblock(client libgoal.Client, round uint64) (bookkeeping.Block, error) { + if _, err := client.WaitForRound(round); err != nil { + return bookkeeping.Block{}, err + } + return client.BookkeepingBlock(round) +} + +func rekeyreg(f *fixtures.RestClientFixture, a *require.Assertions, client libgoal.Client, address string) basics.AccountData { + // we start by making an _offline_ tx here, because we want to populate the + // key material ourself with a copy of the account's existing material. That + // makes it an _online_ keyreg. That allows the running node to chug along + // without new part keys. We overpay the fee, which makes us + // IncentiveEligible, and to get some funds into FeeSink because we will + // watch it drain toward bottom of test. + reReg, err := client.MakeUnsignedGoOfflineTx(address, 0, 0, 12_000_000, [32]byte{}) + a.NoError(err) + + data, err := client.AccountData(address) + a.NoError(err) + a.Equal(basics.Online, data.Status) // must already be online for this to work + a.True(data.LastHeartbeat == 0) + a.False(data.IncentiveEligible) + reReg.KeyregTxnFields = transactions.KeyregTxnFields{ + VotePK: data.VoteID, + SelectionPK: data.SelectionID, + StateProofPK: data.StateProofID, + VoteFirst: data.VoteFirstValid, + VoteLast: data.VoteLastValid, + VoteKeyDilution: data.VoteKeyDilution, + } + + wh, err := client.GetUnencryptedWalletHandle() + a.NoError(err) + onlineTxID, err := client.SignAndBroadcastTransaction(wh, nil, reReg) + a.NoError(err) + txn, err := f.WaitForConfirmedTxn(uint64(reReg.LastValid), onlineTxID) + a.NoError(err) + data, err = client.AccountData(address) + a.NoError(err) + a.Equal(basics.Online, data.Status) + a.True(data.LastHeartbeat > 0) + a.True(data.IncentiveEligible) + fmt.Printf(" %v has %v in round %d\n", address, data.MicroAlgos.Raw, *txn.ConfirmedRound) + return data +} diff --git a/test/e2e-go/features/incentives/suspension_test.go b/test/e2e-go/features/incentives/suspension_test.go new file mode 100644 index 0000000000..3d3f0954f6 --- /dev/null +++ b/test/e2e-go/features/incentives/suspension_test.go @@ -0,0 +1,171 @@ +// Copyright (C) 2019-2024 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 suspension + +import ( + "fmt" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" + "github.com/algorand/go-algorand/data/basics" + "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" +) + +// TestBasicSuspension confirms that accounts that don't propose get suspended +// (when a tx naming them occurs) +func TestBasicSuspension(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + t.Parallel() + a := require.New(fixtures.SynchronizedTest(t)) + + // Overview of this test: + // Start a three-node network (70,20,10), all online + // Wait for 10 and 20% nodes to propose (we never suspend accounts with lastProposed=lastHeartbeat=0) + // Stop them both + // Run for 55 rounds, which is enough for 20% node to be suspended, but not 10% + // check neither suspended, send a tx from 20% to 10%, only 20% gets suspended + // TODO once we have heartbeats: bring them back up, make sure 20% gets back online + const suspend20 = 55 + + var fixture fixtures.RestClientFixture + // Speed up rounds, but keep long lookback, so 20% node has a chance to get + // back online after being suspended. + fixture.FasterConsensus(protocol.ConsensusFuture, time.Second/2, 320) + fixture.Setup(t, filepath.Join("nettemplates", "Suspension.json")) + defer fixture.Shutdown() + + clientAndAccount := func(name string) (libgoal.Client, model.Account) { + c := fixture.GetLibGoalClientForNamedNode(name) + accounts, err := fixture.GetNodeWalletsSortedByBalance(c) + a.NoError(err) + a.Len(accounts, 1) + fmt.Printf("Client %s is %v\n", name, accounts[0].Address) + return c, accounts[0] + } + + c10, account10 := clientAndAccount("Node10") + c20, account20 := clientAndAccount("Node20") + + rekeyreg(&fixture, a, c10, account10.Address) + rekeyreg(&fixture, a, c20, account20.Address) + + // Wait until each have proposed, so they are suspendable + proposed10 := false + proposed20 := false + for !proposed10 || !proposed20 { + status, err := c10.Status() + a.NoError(err) + block, err := c10.BookkeepingBlock(status.LastRound) + a.NoError(err) + + fmt.Printf(" block %d proposed by %v\n", status.LastRound, block.Proposer()) + + fixture.WaitForRoundWithTimeout(status.LastRound + 1) + + switch block.Proposer().String() { + case account10.Address: + proposed10 = true + case account20.Address: + proposed20 = true + } + } + + a.NoError(c20.FullStop()) + + afterStop, err := c10.Status() + a.NoError(err) + + // Advance 55 rounds + err = fixture.WaitForRoundWithTimeout(afterStop.LastRound + suspend20) + a.NoError(err) + + // n20 is still online after 55 rounds of absence (the node is off, but the + // account is marked online) because it has not been "noticed". + account, err := fixture.LibGoalClient.AccountData(account20.Address) + a.NoError(err) + a.Equal(basics.Online, account.Status) + voteID := account.VoteID + a.NotZero(voteID) + + // pay n10 & n20, so both could be noticed + richAccount, err := fixture.GetRichestAccount() + a.NoError(err) + fixture.SendMoneyAndWait(afterStop.LastRound+suspend20, 5, 1000, richAccount.Address, account10.Address, "") + fixture.SendMoneyAndWait(afterStop.LastRound+suspend20, 5, 1000, richAccount.Address, account20.Address, "") + + // n20's account is now offline, but has voting key material (suspended) + account, err = c10.AccountData(account20.Address) + a.NoError(err) + a.Equal(basics.Offline, account.Status) + a.NotZero(account.VoteID) + a.False(account.IncentiveEligible) // suspension turns off flag + + // n10's account is still online, because it's got less stake, has not been absent 10 x interval. + account, err = c10.AccountData(account10.Address) + a.NoError(err) + a.Equal(basics.Online, account.Status) + a.NotZero(account.VoteID) + a.True(account.IncentiveEligible) + + // Use the fixture to start the node again. Since we're only a bit past the + // suspension round, it will still be voting. It should get a chance to + // propose soon (20/100 of blocks) which will put it back online. + lg, err := fixture.StartNode(c20.DataDir()) + a.NoError(err) + + // Wait for newly restarted node to start. + stat, err := lg.Status() + a.NoError(err) + + // Get the current round, and wait for the restarted node to get there. + stat, err = fixture.AlgodClient.Status() + a.NoError(err) + + // Wait for latest round to show n20 has started and caught up. + restartRound := stat.LastRound + stat, err = lg.WaitForRound(restartRound) + a.NoError(err) + + // Proceed until a round is proposed by n20. + attempts := 0 + for !fixture.VerifyBlockProposed(account20.Address, 1) { + stat, err = lg.WaitForRound(stat.LastRound + 1) + a.NoError(err) + attempts++ + a.Less(attempts, suspend20, "n20 didn't propose\n") + } + // paranoia. see payouts_test.go for more details. + r := require.New(t) + for i, c := range []libgoal.Client{c10, c20} { + account, err = c.AccountData(account20.Address) + a.NoError(err) + r.Equal(basics.Online, account.Status, i) + r.Greater(account.LastProposed, restartRound, i) + + r.Equal(voteID, account.VoteID, i) + r.False(account.IncentiveEligible, i) + } +} diff --git a/test/e2e-go/features/multisig/multisig_test.go b/test/e2e-go/features/multisig/multisig_test.go index 6d7b1c616d..6264b70161 100644 --- a/test/e2e-go/features/multisig/multisig_test.go +++ b/test/e2e-go/features/multisig/multisig_test.go @@ -91,7 +91,7 @@ func TestBasicMultisig(t *testing.T) { txid, err := client.BroadcastTransaction(signedTransactionWithTwo) r.NoError(err, "Trying to broadcast 2-of-3 multisig with 2 sig should not cause error") curStatus, _ = client.Status() - r.True(fixture.WaitForTxnConfirmation(curStatus.LastRound+uint64(5), multisigAddr, txid)) + r.True(fixture.WaitForTxnConfirmation(curStatus.LastRound+uint64(5), txid)) // Need a new txid to avoid dup detection unsignedTransaction, err = client.ConstructPayment(multisigAddr, addrs[0], minTxnFee, amountToSend, []byte("foobar"), "", [32]byte{}, 0, 0) diff --git a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go index 38f2e4bc35..0b38fe76ff 100644 --- a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go +++ b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go @@ -196,7 +196,7 @@ func TestNewAccountCanGoOnlineAndParticipate(t *testing.T) { fixture.AssertValidTxid(onlineTxID) maxRoundsToWaitForTxnConfirm := uint64(5) - fixture.WaitForTxnConfirmation(seededRound+maxRoundsToWaitForTxnConfirm, newAccount, onlineTxID) + fixture.WaitForTxnConfirmation(seededRound+maxRoundsToWaitForTxnConfirm, onlineTxID) nodeStatus, _ = client.Status() onlineRound := nodeStatus.LastRound newAccountStatus, err := client.AccountInformation(newAccount, false) @@ -247,11 +247,7 @@ func TestAccountGoesOnlineForShortPeriod(t *testing.T) { t.Parallel() a := require.New(fixtures.SynchronizedTest(t)) - // Make the seed lookback shorter, otherwise will wait for 320 rounds - consensus := make(config.ConsensusProtocols) - var fixture fixtures.RestClientFixture - fixture.SetConsensus(consensus) fixture.SetupNoStart(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) // update the config file by setting the ParticipationKeysRefreshInterval to 5 second. @@ -311,7 +307,7 @@ func TestAccountGoesOnlineForShortPeriod(t *testing.T) { nodeStatus, err := client.Status() a.NoError(err) seededRound := nodeStatus.LastRound - fixture.WaitForTxnConfirmation(seededRound+maxRoundsToWaitForTxnConfirm, newAccount, onlineTxID) + fixture.WaitForTxnConfirmation(seededRound+maxRoundsToWaitForTxnConfirm, onlineTxID) nodeStatus, _ = client.Status() accountStatus, err := client.AccountInformation(newAccount, false) diff --git a/test/e2e-go/features/participation/participationExpiration_test.go b/test/e2e-go/features/participation/participationExpiration_test.go index 5cb4f941dc..e0569cf040 100644 --- a/test/e2e-go/features/participation/participationExpiration_test.go +++ b/test/e2e-go/features/participation/participationExpiration_test.go @@ -111,7 +111,7 @@ func testExpirationAccounts(t *testing.T, fixture *fixtures.RestClientFixture, f a.NoError(err) seededRound := sNodeStatus.LastRound - txnConfirmed := fixture.WaitForTxnConfirmation(seededRound+maxRoundsToWaitForTxnConfirm, sAccount, onlineTxID) + txnConfirmed := fixture.WaitForTxnConfirmation(seededRound+maxRoundsToWaitForTxnConfirm, onlineTxID) a.True(txnConfirmed) newAccountStatus, err = pClient.AccountInformation(sAccount, false) @@ -156,7 +156,7 @@ func testExpirationAccounts(t *testing.T, fixture *fixtures.RestClientFixture, f sendMoneyTxn := fixture.SendMoneyAndWait(latestRound, amountToSendInitial, transactionFee, richAccount, sAccount, "") - txnConfirmed = fixture.WaitForTxnConfirmation(latestRound+maxRoundsToWaitForTxnConfirm, sAccount, sendMoneyTxn.Txn.ID().String()) + txnConfirmed = fixture.WaitForTxnConfirmation(latestRound+maxRoundsToWaitForTxnConfirm, sendMoneyTxn.Txn.ID().String()) a.True(txnConfirmed) newAccountStatus, err = pClient.AccountInformation(sAccount, false) diff --git a/test/e2e-go/features/participation/participationRewards_test.go b/test/e2e-go/features/participation/participationRewards_test.go index 8c0fb64a8d..043f58f3c7 100644 --- a/test/e2e-go/features/participation/participationRewards_test.go +++ b/test/e2e-go/features/participation/participationRewards_test.go @@ -181,7 +181,7 @@ func TestPartkeyOnlyRewards(t *testing.T) { // do a balance poke by moving funds b/w accounts. this will cause balances to reflect received rewards tx, err := fixture.LibGoalClient.SendPaymentFromUnencryptedWallet(richAccount.Address, account.String(), minFee, minBalance, nil) r.NoError(err) - fixture.WaitForTxnConfirmation(arbitraryPostGenesisRound+uint64(10), tx.ID().String(), richAccount.Address) + fixture.WaitForTxnConfirmation(arbitraryPostGenesisRound+uint64(10), tx.ID().String()) finalBalance, err := client.GetBalance(account.String()) r.NoError(err) delta := finalBalance - initialBalance diff --git a/test/e2e-go/features/transactions/accountv2_test.go b/test/e2e-go/features/transactions/accountv2_test.go index fb255fa9c2..97eb1b4bd8 100644 --- a/test/e2e-go/features/transactions/accountv2_test.go +++ b/test/e2e-go/features/transactions/accountv2_test.go @@ -114,7 +114,7 @@ func TestAccountInformationV2(t *testing.T) { round, err := client.CurrentRound() a.NoError(err) - fixture.WaitForConfirmedTxn(round+4, creator, txn.ID().String()) + fixture.WaitForConfirmedTxn(round+4, txn.ID().String()) // There should be no apps to start with ad, err := client.AccountData(creator) @@ -419,7 +419,7 @@ func accountInformationCheckWithOffendingFields(t *testing.T, round, err := client.CurrentRound() a.NoError(err) - fixture.WaitForConfirmedTxn(round+4, creator, txn.ID().String()) + fixture.WaitForConfirmedTxn(round+4, txn.ID().String()) // There should be no apps to start with ad, err := client.AccountData(creator) diff --git a/test/e2e-go/features/transactions/app_pages_test.go b/test/e2e-go/features/transactions/app_pages_test.go index 710c00fc7f..5058fb59b8 100644 --- a/test/e2e-go/features/transactions/app_pages_test.go +++ b/test/e2e-go/features/transactions/app_pages_test.go @@ -95,7 +95,7 @@ return a.NoError(err) txid, err := client.SignAndBroadcastTransaction(walletHandle, nil, tx) a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(status.LastRound+5, baseAcct, txid) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+5, txid) a.NoError(err) app1CreateTxn, err := client.PendingTransactionInformation(txid) @@ -116,7 +116,7 @@ return a.NoError(err) txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx) a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(*app1CreateTxn.ConfirmedRound+5, baseAcct, txid) + _, err = fixture.WaitForConfirmedTxn(*app1CreateTxn.ConfirmedRound+5, txid) a.NoError(err) app1UpdateTxn, err := client.PendingTransactionInformation(txid) @@ -136,7 +136,7 @@ return a.NoError(err) txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx) a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(*app1UpdateTxn.ConfirmedRound+5, baseAcct, txid) + _, err = fixture.WaitForConfirmedTxn(*app1UpdateTxn.ConfirmedRound+5, txid) a.NoError(err) app2CreateTxn, err := client.PendingTransactionInformation(txid) @@ -157,7 +157,7 @@ return a.NoError(err) txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx) a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(*app2CreateTxn.ConfirmedRound+5, baseAcct, txid) + _, err = fixture.WaitForConfirmedTxn(*app2CreateTxn.ConfirmedRound+5, txid) a.NoError(err) app1DeleteTxn, err := client.PendingTransactionInformation(txid) @@ -176,7 +176,7 @@ return a.NoError(err) txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx) a.NoError(err) - _, err = fixture.WaitForConfirmedTxn(*app1DeleteTxn.ConfirmedRound+5, baseAcct, txid) + _, err = fixture.WaitForConfirmedTxn(*app1DeleteTxn.ConfirmedRound+5, txid) a.NoError(err) accountInfo, err = client.AccountInformation(baseAcct, false) diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 82e949d0bb..11baa9b7e4 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -774,7 +774,7 @@ func TestAssetSend(t *testing.T) { tx, err = client.SendPaymentFromUnencryptedWallet(account0, extra, 0, 10000000000, nil) a.NoError(err) _, curRound = fixture.GetBalanceAndRound(account0) - fixture.WaitForConfirmedTxn(curRound+20, account0, tx.ID().String()) + fixture.WaitForConfirmedTxn(curRound+20, tx.ID().String()) // Sending assets to account that hasn't opted in should fail, but // after opting in, should succeed for non-frozen asset. diff --git a/test/e2e-go/features/transactions/close_account_test.go b/test/e2e-go/features/transactions/close_account_test.go index a0f42a40d5..c15a39288d 100644 --- a/test/e2e-go/features/transactions/close_account_test.go +++ b/test/e2e-go/features/transactions/close_account_test.go @@ -64,11 +64,11 @@ func TestAccountsCanClose(t *testing.T) { // Transfer some money to acct0 and wait. tx, err := client.SendPaymentFromUnencryptedWallet(baseAcct, acct0, 1000, 10000000, nil) a.NoError(err) - fixture.WaitForConfirmedTxn(status.LastRound+10, baseAcct, tx.ID().String()) + fixture.WaitForConfirmedTxn(status.LastRound+10, tx.ID().String()) tx, err = client.SendPaymentFromWallet(walletHandle, nil, acct0, acct1, 1000, 1000000, nil, acct2, 0, 0) a.NoError(err) - fixture.WaitForConfirmedTxn(status.LastRound+10, acct0, tx.ID().String()) + fixture.WaitForConfirmedTxn(status.LastRound+10, tx.ID().String()) bal0, err := client.GetBalance(acct0) a.NoError(err) diff --git a/test/e2e-go/features/transactions/proof_test.go b/test/e2e-go/features/transactions/proof_test.go index 6588174f36..4f6226f139 100644 --- a/test/e2e-go/features/transactions/proof_test.go +++ b/test/e2e-go/features/transactions/proof_test.go @@ -92,7 +92,7 @@ func TestTxnMerkleProof(t *testing.T) { txid := tx.ID() txidSHA256 := tx.IDSha256() // only used for verification - confirmedTx, err := fixture.WaitForConfirmedTxn(status.LastRound+10, baseAcct, txid.String()) + confirmedTx, err := fixture.WaitForConfirmedTxn(status.LastRound+10, txid.String()) a.NoError(err) a.NotNil(confirmedTx.ConfirmedRound) @@ -175,7 +175,7 @@ func TestTxnMerkleProofSHA256(t *testing.T) { a.NoError(err) txid := tx.ID() - confirmedTx, err := fixture.WaitForConfirmedTxn(status.LastRound+10, baseAcct, txid.String()) + confirmedTx, err := fixture.WaitForConfirmedTxn(status.LastRound+10, txid.String()) a.NoError(err) a.NotNil(confirmedTx.ConfirmedRound) diff --git a/test/e2e-go/features/transactions/sendReceive_test.go b/test/e2e-go/features/transactions/sendReceive_test.go index 9d909beedc..22dc94173a 100644 --- a/test/e2e-go/features/transactions/sendReceive_test.go +++ b/test/e2e-go/features/transactions/sendReceive_test.go @@ -81,7 +81,7 @@ func testAccountsCanSendMoney(t *testing.T, templatePath string, numberOfSends i pingAccount := pingAccountList[0].Address pongClient := fixture.GetLibGoalClientForNamedNode("Node") - pongAccounts, err := fixture.GetNodeWalletsSortedByBalance(pongClient.DataDir()) + pongAccounts, err := fixture.GetNodeWalletsSortedByBalance(pongClient) a.NoError(err) var pongAccount string for _, acct := range pongAccounts { diff --git a/test/e2e-go/perf/basic_test.go b/test/e2e-go/perf/basic_test.go index 8509c40006..bdb8067c40 100644 --- a/test/e2e-go/perf/basic_test.go +++ b/test/e2e-go/perf/basic_test.go @@ -231,7 +231,7 @@ func doBenchTemplate(b *testing.B, template string, moneynode string) { time.Sleep(5 * time.Second) } - _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, addr, tx.ID().String()) + _, err = fixture.WaitForConfirmedTxn(status.LastRound+100, tx.ID().String()) fmt.Printf("Waiting for confirmation transaction to commit..\n") a.NoError(err) } diff --git a/test/e2e-go/upgrades/application_support_test.go b/test/e2e-go/upgrades/application_support_test.go index 49635a43ac..549a82c5ab 100644 --- a/test/e2e-go/upgrades/application_support_test.go +++ b/test/e2e-go/upgrades/application_support_test.go @@ -85,7 +85,7 @@ func TestApplicationsUpgradeOverREST(t *testing.T) { a := require.New(fixtures.SynchronizedTest(t)) client := fixture.GetLibGoalClientForNamedNode("Node") - accountList, err := fixture.GetNodeWalletsSortedByBalance(client.DataDir()) + accountList, err := fixture.GetNodeWalletsSortedByBalance(client) a.NoError(err) creator := accountList[0].Address @@ -328,7 +328,7 @@ func TestApplicationsUpgradeOverGossip(t *testing.T) { defer fixture.Shutdown() - accountList, err := fixture.GetNodeWalletsSortedByBalance(client.DataDir()) + accountList, err := fixture.GetNodeWalletsSortedByBalance(client) a.NoError(err) creator := accountList[0].Address @@ -454,7 +454,7 @@ int 1 // Try polling 10 rounds to ensure txn is committed. round, err = client.CurrentRound() a.NoError(err) - isCommitted := fixture.WaitForTxnConfirmation(round+10, creator, txid) + isCommitted := fixture.WaitForTxnConfirmation(round+10, txid) a.True(isCommitted) // check creator's balance record for the app entry and the state changes diff --git a/test/e2e-go/upgrades/rekey_support_test.go b/test/e2e-go/upgrades/rekey_support_test.go index 30226c795f..0dcec41545 100644 --- a/test/e2e-go/upgrades/rekey_support_test.go +++ b/test/e2e-go/upgrades/rekey_support_test.go @@ -46,7 +46,7 @@ func TestRekeyUpgrade(t *testing.T) { defer fixture.Shutdown() client := fixture.GetLibGoalClientForNamedNode("Node") - accountList, err := fixture.GetNodeWalletsSortedByBalance(client.DataDir()) + accountList, err := fixture.GetNodeWalletsSortedByBalance(client) a.NoError(err) accountA := accountList[0].Address diff --git a/test/e2e-go/upgrades/send_receive_upgrade_test.go b/test/e2e-go/upgrades/send_receive_upgrade_test.go index f9e0746cbc..86127aba8b 100644 --- a/test/e2e-go/upgrades/send_receive_upgrade_test.go +++ b/test/e2e-go/upgrades/send_receive_upgrade_test.go @@ -278,12 +278,12 @@ func runUntilProtocolUpgrades(a *require.Assertions, fixture *fixtures.RestClien // wait for all transactions to confirm for _, txid := range pingTxids { - _, err = fixture.WaitForConfirmedTxn(curStatus.LastRound+5, pingAccount, txid) + _, err = fixture.WaitForConfirmedTxn(curStatus.LastRound+5, txid) a.NoError(err, "waiting for txn") } for _, txid := range pongTxids { - _, err = fixture.WaitForConfirmedTxn(curStatus.LastRound+5, pongAccount, txid) + _, err = fixture.WaitForConfirmedTxn(curStatus.LastRound+5, txid) a.NoError(err, "waiting for txn") } diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index bb96058136..1ffc6493c0 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -22,6 +22,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" "syscall" "testing" @@ -35,6 +36,7 @@ import ( "github.com/algorand/go-algorand/crypto/merklearray" "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/gen" "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/netdeploy" @@ -65,6 +67,28 @@ func (f *RestClientFixture) SetConsensus(consensus config.ConsensusProtocols) { f.consensus = consensus } +// FasterConsensus speeds up the given consensus version in two ways. The seed +// refresh lookback is set to 8 (instead of 80), so the 320 round balance +// lookback becomes 32. And, if the architecture implies it can be handled, +// round times are shortened by lowering vote timeouts. +func (f *RestClientFixture) FasterConsensus(ver protocol.ConsensusVersion, timeout time.Duration, lookback basics.Round) { + if f.consensus == nil { + f.consensus = make(config.ConsensusProtocols) + } + fast := config.Consensus[ver] + // balanceRound is 4 * SeedRefreshInterval + if lookback%4 != 0 { + panic(fmt.Sprintf("lookback must be a multiple of 4, got %d", lookback)) + } + fast.SeedRefreshInterval = uint64(lookback) / 4 + // and speed up the rounds while we're at it + if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" { + fast.AgreementFilterTimeoutPeriod0 = timeout + fast.AgreementFilterTimeout = timeout + } + f.consensus[ver] = fast +} + // Setup is called to initialize the test fixture for the test(s) func (f *LibGoalFixture) Setup(t TestingTB, templateFile string) { f.setup(t, t.Name(), templateFile, true) diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 40e12c408b..38e89c35e5 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -196,16 +196,12 @@ func (f *RestClientFixture) GetBalanceAndRound(account string) (balance uint64, // GetWalletsSortedByBalance returns the Primary node's accounts sorted DESC by balance // the richest account will be at accounts[0] func (f *RestClientFixture) GetWalletsSortedByBalance() (accounts []model.Account, err error) { - return f.getNodeWalletsSortedByBalance(f.LibGoalClient) + return f.GetNodeWalletsSortedByBalance(f.LibGoalClient) } // GetNodeWalletsSortedByBalance returns the specified node's accounts sorted DESC by balance // the richest account will be at accounts[0] -func (f *RestClientFixture) GetNodeWalletsSortedByBalance(nodeDataDir string) (accounts []model.Account, err error) { - return f.getNodeWalletsSortedByBalance(f.GetLibGoalClientFromDataDir(nodeDataDir)) -} - -func (f *RestClientFixture) getNodeWalletsSortedByBalance(client libgoal.Client) (accounts []model.Account, err error) { +func (f *RestClientFixture) GetNodeWalletsSortedByBalance(client libgoal.Client) (accounts []model.Account, err error) { wh, err := client.GetUnencryptedWalletHandle() if err != nil { return nil, fmt.Errorf("unable to retrieve wallet handle : %v", err) @@ -228,15 +224,15 @@ func (f *RestClientFixture) getNodeWalletsSortedByBalance(client libgoal.Client) // WaitForTxnConfirmation waits until either the passed txid is confirmed // or until the passed roundTimeout passes // or until waiting for a round to pass times out -func (f *RestClientFixture) WaitForTxnConfirmation(roundTimeout uint64, accountAddress, txid string) bool { - _, err := f.WaitForConfirmedTxn(roundTimeout, accountAddress, txid) +func (f *RestClientFixture) WaitForTxnConfirmation(roundTimeout uint64, txid string) bool { + _, err := f.WaitForConfirmedTxn(roundTimeout, txid) return err == nil } // WaitForConfirmedTxn waits until either the passed txid is confirmed // or until the passed roundTimeout passes // or until waiting for a round to pass times out -func (f *RestClientFixture) WaitForConfirmedTxn(roundTimeout uint64, accountAddress, txid string) (txn v2.PreEncodedTxInfo, err error) { +func (f *RestClientFixture) WaitForConfirmedTxn(roundTimeout uint64, txid string) (txn v2.PreEncodedTxInfo, err error) { client := f.AlgodClient for { // Get current round information @@ -274,7 +270,7 @@ func (f *RestClientFixture) WaitForAllTxnsToConfirm(roundTimeout uint64, txidsAn return true } for txid, addr := range txidsAndAddresses { - _, err := f.WaitForConfirmedTxn(roundTimeout, addr, txid) + _, err := f.WaitForConfirmedTxn(roundTimeout, txid) if err != nil { f.t.Logf("txn failed to confirm: addr=%s, txid=%s", addr, txid) pendingTxns, err := f.LibGoalClient.GetParsedPendingTransactions(0) @@ -363,7 +359,7 @@ func (f *RestClientFixture) SendMoneyAndWaitFromWallet(walletHandle, walletPassw require.NoError(f.t, err, "client should be able to send money from rich to poor account") require.NotEmpty(f.t, fundingTx.ID().String(), "transaction ID should not be empty") waitingDeadline := curRound + uint64(5) - txn, err = f.WaitForConfirmedTxn(waitingDeadline, fromAccount, fundingTx.ID().String()) + txn, err = f.WaitForConfirmedTxn(waitingDeadline, fundingTx.ID().String()) require.NoError(f.t, err) return } diff --git a/test/scripts/e2e_subs/absentee.py b/test/scripts/e2e_subs/absentee.py new file mode 100755 index 0000000000..7e72d8a966 --- /dev/null +++ b/test/scripts/e2e_subs/absentee.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import os +import sys +from goal import Goal + +from datetime import datetime + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} start {stamp}") + +goal = Goal(sys.argv[1], autosend=True) + +joe = goal.new_account() + +txinfo, err = goal.pay(goal.account, joe, amt=500_000) +assert not err, err + +# Joe is a brand new account, it has neither proposed nor heartbeat +joe_info = goal.algod.account_info(joe) +assert "last-proposed" not in joe_info, joe_info +assert "last-heartbeat" not in joe_info, joe_info + +# Find info on the proposer of the pay block +pblock = goal.algod.block_info(txinfo['confirmed-round'])['block'] +assert pblock["prp"] != "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ" +prp_info = goal.algod.account_info(pblock["prp"]) +assert prp_info["round"] == pblock["rnd"], pblock +assert "last-proposed" in prp_info, prp_info # they just did! +assert prp_info["last-proposed"] > 0 +assert "last-heartbeat" not in prp_info, prp_info # was a genesis account + +# This test really only examines the fields needed for absenteeism +# tracking. For actually seeing accounts being taken offline, see +# `suspension_test.go` + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/eligible.py b/test/scripts/e2e_subs/eligible.py new file mode 100755 index 0000000000..ddac9a3515 --- /dev/null +++ b/test/scripts/e2e_subs/eligible.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import base64 +import os +import sys +from goal import Goal +import algosdk.encoding as enc + +from datetime import datetime + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} start {stamp}") + +goal = Goal(sys.argv[1], autosend=True) + +joe = goal.new_account() + +txinfo, err = goal.pay(goal.account, joe, amt=10_000_000) +assert not err, err + +# Joe is a brand new account, it is not incentive eligible +joe_info = goal.algod.account_info(joe) +assert "incentive-eligible" not in joe_info, joe_info + +# Go online, but without paying enough to be incentive eligible +txinfo, err = goal.keyreg(joe, votekey=base64.b64encode(b'1'*32), + selkey=base64.b64encode(b'1'*32), + sprfkey=base64.b64encode(b'1'*64), + votekd=1, + votefst=1, votelst=2000) +assert not err, err + +# No extra fee paid, so not eligible +joe_info = goal.algod.account_info(joe) +assert "incentive-eligible" not in joe_info, joe_info + +# Pay the extra fee to become eligible +txinfo, err = goal.keyreg(joe, fee=3_000_000, + votekey=base64.b64encode(b'1'*32), + selkey=base64.b64encode(b'1'*32), + sprfkey=base64.b64encode(b'1'*64), + votekd=2, + votefst=1, votelst=2000) +assert not err, err +joe_info = goal.algod.account_info(joe) +assert joe_info.get("incentive-eligible", None) == True, joe_info + + + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/goal/goal.py b/test/scripts/e2e_subs/goal/goal.py index 5d0dd7db0f..57d8b0acb5 100755 --- a/test/scripts/e2e_subs/goal/goal.py +++ b/test/scripts/e2e_subs/goal/goal.py @@ -240,21 +240,21 @@ def finish(self, tx, send): return tx def keyreg(self, sender, votekey=None, selkey=None, votefst=None, - votelst=None, votekd=None, + votelst=None, votekd=None, sprfkey=None, send=None, **kwargs): - params = self.algod.suggested_params() + params = self.params(kwargs.pop("lifetime", 1000), kwargs.pop("fee", None)) tx = txn.KeyregTxn(sender, params, - votekey, selkey, votefst, votelst, votekd, + votekey, selkey, votefst, votelst, votekd, sprfkey=sprfkey, **kwargs) return self.finish(tx, send) def pay(self, sender, receiver, amt: int, send=None, **kwargs): - params = self.algod.suggested_params() + params = self.params(kwargs.pop("lifetime", 1000), kwargs.pop("fee", None)) tx = txn.PaymentTxn(sender, params, receiver, amt, **kwargs) return self.finish(tx, send) def acfg(self, sender, send=None, **kwargs): - params = self.algod.suggested_params() + params = self.params(kwargs.pop("lifetime", 1000), kwargs.pop("fee", None)) tx = txn.AssetConfigTxn( sender, params, **kwargs, strict_empty_address_check=False ) @@ -265,7 +265,7 @@ def asset_create(self, sender, **kwargs): return self.acfg(sender, **kwargs) def axfer(self, sender, receiver, amt: int, index: int, send=None, **kwargs): - params = self.algod.suggested_params() + params = self.params(kwargs.pop("lifetime", 1000), kwargs.pop("fee", None)) tx = txn.AssetTransferTxn( sender, params, receiver, amt, index, **kwargs ) @@ -276,7 +276,7 @@ def asset_optin(self, sender, index: int, **kwargs): return self.axfer(sender, sender, 0, index, **kwargs) def afrz(self, sender, index: int, target, frozen, send=None, **kwargs): - params = self.algod.suggested_params() + params = self.params(kwargs.pop("lifetime", 1000), kwargs.pop("fee", None)) tx = txn.AssetFreezeTxn(sender, params, index, target, frozen, **kwargs) return self.finish(tx, send) @@ -287,9 +287,19 @@ def coerce_schema(self, values): return values return txn.StateSchema(num_uints=values[0], num_byte_slices=values[1]) + + def params(self, lifetime=None, fee=None): + params = self.algod.suggested_params() + if lifetime is not None: + params.last = params.first + lifetime + if fee is not None: + params.flat_fee = True + params.fee = fee + return params + def appl(self, sender, index: int, on_complete=txn.OnComplete.NoOpOC, send=None, **kwargs): - params = self.algod.suggested_params() + params = self.params(kwargs.pop("lifetime", 1000), kwargs.pop("fee", None)) local_schema = self.coerce_schema(kwargs.pop("local_schema", None)) global_schema = self.coerce_schema(kwargs.pop("global_schema", None)) tx = txn.ApplicationCallTxn( diff --git a/test/scripts/e2e_subs/htlc-teal-test.sh b/test/scripts/e2e_subs/htlc-teal-test.sh index 9dfd62a574..d2b70c533d 100755 --- a/test/scripts/e2e_subs/htlc-teal-test.sh +++ b/test/scripts/e2e_subs/htlc-teal-test.sh @@ -47,7 +47,7 @@ ${gcmd} clerk send --fee=1000 --from-program ${TEMPDIR}/atomic.teal -a=0 -t=${ZE # Check balance BALANCEB=$(${gcmd} account balance -a ${ACCOUNTB} | awk '{ print $1 }') if [ $BALANCEB -ne 9999000 ]; then - date '+htlc-teal-test FAIL wanted balance=9999000 but got ${BALANCEB} %Y%m%d_%H%M%S' + date "+htlc-teal-test FAIL wanted balance=9999000 but got ${BALANCEB} %Y%m%d_%H%M%S" false fi diff --git a/test/scripts/e2e_subs/payouts.py b/test/scripts/e2e_subs/payouts.py new file mode 100755 index 0000000000..216ecc93ef --- /dev/null +++ b/test/scripts/e2e_subs/payouts.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +import base64 +import os +import sys +from goal import Goal +import algosdk.encoding as enc + +from datetime import datetime + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} start {stamp}") + +goal = Goal(sys.argv[1], autosend=True) + +joe = goal.new_account() + +_, err = goal.pay(goal.account, joe, amt=500_000) +assert not err, err + +# Turn off rewards for precise balance checking +_, err = goal.keyreg(joe, nonpart=True) +assert not err, err + +get_proposer = """ +#pragma version 11 + txn ApplicationArgs 0; btoi + block BlkProposer; global ZeroAddress; !=; assert + + txn ApplicationArgs 0; btoi + block BlkProposer; log + + txn ApplicationArgs 0; btoi + block BlkFeesCollected; itob; log + + int 1 +""" + + + +# During construction, the app examines an arbitrary round, a little before the latest. +examined = max(goal.params().first-5, 1) +txinfo, err = goal.app_create(joe, goal.assemble(get_proposer), app_args=[examined], lifetime=50) +assert not err, err +getter = txinfo['application-index'] +assert getter + +# There should be two logs, the proposer of the examined round, and the fees from that round +rnd = txinfo['confirmed-round'] +# Look at the block of the creation. We know fees collected is non-zero +block = goal.algod.block_info(rnd)['block'] +assert "fc" in block +assert block["fc"] > 0 # We don't test exact, because other tests are running +assert "prp" in block +assert block["prp"] != "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ" + +create_proposer = block["prp"] +immediately_after = goal.balance(create_proposer) +assert immediately_after > 10000000 # Our proposers in e2e tests have pretty much all the money + +# Compare the examined block's header to what the AVM saw (and logged) +block = goal.algod.block_info(examined)['block'] +print("creation", txinfo['logs'], block) +assert base64.b64decode(txinfo['logs'][0]) == enc.decode_address(block['prp']) +assert base64.b64decode(txinfo['logs'][1]) == block.get('fc',0).to_bytes(8, "big") + +# Now have the app examine the round the app was constructed, so we +# can check the log and know there should be a fee. +goal.wait_for_block(rnd+1) # because fv is set to current latest (rnd), so it `block rnd` wouldn't work +txinfo, err = goal.app_call(joe, getter, app_args=[rnd], lifetime=10) +assert not err, err + +block = goal.algod.block_info(rnd)['block'] +# note we use block['fc'], not block.get('fc', 0) +print("call", txinfo['logs'], block) +assert base64.b64decode(txinfo['logs'][0]) == enc.decode_address(block['prp']) +assert base64.b64decode(txinfo['logs'][1]) == block['fc'].to_bytes(8, "big") + +# We can not do checks on whether the proposer actually gets paid here +# because in our e2e tests, the proposers _won't_ get paid. Their +# accounts have too many algos. + +stamp = datetime.now().strftime("%Y%m%d_%H%M%S") +print(f"{os.path.basename(sys.argv[0])} OK {stamp}") diff --git a/test/scripts/e2e_subs/periodic-teal-test.sh b/test/scripts/e2e_subs/periodic-teal-test.sh index 7ec6512ac1..6e95e7658d 100755 --- a/test/scripts/e2e_subs/periodic-teal-test.sh +++ b/test/scripts/e2e_subs/periodic-teal-test.sh @@ -48,7 +48,7 @@ done BALANCEB=$(${gcmd} account balance -a ${ACCOUNTB}|awk '{ print $1 }') if [ $BALANCEB -ne 300000 ]; then - date '+periodic-teal-test FAIL wanted balance=3000000 but got ${BALANCEB} %Y%m%d_%H%M%S' + date "+periodic-teal-test FAIL wanted balance=3000000 but got ${BALANCEB} %Y%m%d_%H%M%S" false fi diff --git a/test/testdata/nettemplates/Payouts.json b/test/testdata/nettemplates/Payouts.json new file mode 100644 index 0000000000..586370921e --- /dev/null +++ b/test/testdata/nettemplates/Payouts.json @@ -0,0 +1,28 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "future", + "LastPartKeyRound": 500, + "Wallets": [ + { "Name": "Relay", "Stake": 84, "Online": false }, + { "Name": "Wallet15", "Stake": 15, "Online": true }, + { "Name": "Wallet01", "Stake": 1, "Online": true } + ], + "RewardsPoolBalance": 0 + }, + "Nodes": [ + { + "Name": "Relay", + "Wallets": [{ "Name": "Relay", "ParticipationOnly": false }], + "IsRelay": true + }, + { + "Name": "Node15", + "Wallets": [{ "Name": "Wallet15", "ParticipationOnly": false }] + }, + { + "Name": "Node01", + "Wallets": [{ "Name": "Wallet01", "ParticipationOnly": false }] + } + ] +} diff --git a/test/testdata/nettemplates/Suspension.json b/test/testdata/nettemplates/Suspension.json new file mode 100644 index 0000000000..439cbb888b --- /dev/null +++ b/test/testdata/nettemplates/Suspension.json @@ -0,0 +1,28 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "future", + "LastPartKeyRound": 500, + "Wallets": [ + { "Name": "Relay", "Stake": 70, "Online": true }, + { "Name": "Wallet20", "Stake": 20, "Online": true }, + { "Name": "Wallet10", "Stake": 10, "Online": true } + ], + "RewardsPoolBalance": 0 + }, + "Nodes": [ + { + "Name": "Relay", + "Wallets": [{ "Name": "Relay", "ParticipationOnly": false }], + "IsRelay": true + }, + { + "Name": "Node20", + "Wallets": [{ "Name": "Wallet20", "ParticipationOnly": false }] + }, + { + "Name": "Node10", + "Wallets": [{ "Name": "Wallet10", "ParticipationOnly": false }] + } + ] +} diff --git a/tools/block-generator/generator/generator_ledger.go b/tools/block-generator/generator/generator_ledger.go index f841b574f3..472ce3f0d5 100644 --- a/tools/block-generator/generator/generator_ledger.go +++ b/tools/block-generator/generator/generator_ledger.go @@ -178,21 +178,22 @@ func (g *generator) evaluateBlock(hdr bookkeeping.BlockHeader, txGroups [][]txn. } for i, txGroup := range txGroups { for { - err := eval.TransactionGroup(txGroup) - if err != nil { - if strings.Contains(err.Error(), "database table is locked") { + txErr := eval.TransactionGroup(txGroup) + if txErr != nil { + if strings.Contains(txErr.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) + return nil, 0, 0, fmt.Errorf("could not evaluate transaction group %d: %w", i, txErr) } break } } - lvb, err := eval.GenerateBlock() - return lvb, eval.TestingTxnCounter(), commitWaitTime, err + ub, err := eval.GenerateBlock(nil) + lvb := ledgercore.MakeValidatedBlock(ub.UnfinishedBlock(), ub.UnfinishedDeltas()) + return &lvb, eval.TestingTxnCounter(), commitWaitTime, err } func countInners(ad txn.ApplyData) int { diff --git a/tools/debug/transplanter/main.go b/tools/debug/transplanter/main.go index 0d2880412d..1a41504c99 100644 --- a/tools/debug/transplanter/main.go +++ b/tools/debug/transplanter/main.go @@ -421,7 +421,7 @@ func main() { txCount := 0 totalTxCount := 0 blockCount := 0 - pool := pools.MakeTransactionPool(l, cfg, log) + pool := pools.MakeTransactionPool(l, cfg, log, nil) hdr, err := l.BlockHdr(l.Latest()) if err != nil { fmt.Fprintf(os.Stderr, "Cannot get latest block header: %v", err) @@ -455,13 +455,15 @@ func main() { if txCount >= *blockSize { deadline := time.Now().Add(100 * time.Millisecond) - vb, err := pool.AssembleBlock(nextRound, deadline) + ab, err := pool.AssembleBlock(nextRound, deadline) if err != nil { fmt.Fprintf(os.Stderr, "ERR: Cannot assemble block %d: %v\n", nextRound, err) break } + // make validated block without calling FinishBlock + vb := ledgercore.MakeValidatedBlock(ab.UnfinishedBlock(), ab.UnfinishedDeltas()) - err = l.AddValidatedBlock(*vb, agreement.Certificate{}) + err = l.AddValidatedBlock(vb, agreement.Certificate{}) if err != nil { fmt.Fprintf(os.Stderr, "ERR: Cannot add block %d: %v\n", nextRound, err) break From 82f4cde6a443714ab83af89c59414b7dabe55d80 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 25 Apr 2024 09:24:47 -0400 Subject: [PATCH 108/218] Tests: Fix `AccountsUpdateSchemaTest` ignoring error bug (#5985) --- ledger/ledger_test.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 8a855ceadd..0e3b066b80 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -2231,11 +2231,7 @@ func TestLedgerReloadTxTailHistoryAccess(t *testing.T) { return err0 } - if err0 := tx.Testing().AccountsUpdateSchemaTest(ctx); err != nil { - return err0 - } - - return nil + return tx.Testing().AccountsUpdateSchemaTest(ctx) }) require.NoError(t, err) From adf39d24c322e915809adc0567f3c3c2e4100f6a Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:40:27 -0400 Subject: [PATCH 109/218] node: remove unused node GetTransaction and ListTxns methods (#5983) --- daemon/algod/api/server/v2/test/helpers.go | 42 ------------- node/node.go | 71 ---------------------- 2 files changed, 113 deletions(-) diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index 400906d299..ad028fc8ae 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -24,7 +24,6 @@ import ( "github.com/stretchr/testify/mock" - "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" @@ -36,7 +35,6 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "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/ledger/simulation" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" @@ -95,7 +93,6 @@ type mockNode struct { err error id account.ParticipationID keys account.StateProofKeys - usertxns map[basics.Address][]node.TxnWithStatus status node.StatusReport devmode bool timestampOffset *int64 @@ -148,7 +145,6 @@ func makeMockNodeWithConfig(ledger v2.LedgerForAPI, genesisID string, nodeError genesisID: genesisID, config: cfg, err: nodeError, - usertxns: map[basics.Address][]node.TxnWithStatus{}, status: status, devmode: devMode, } @@ -199,44 +195,6 @@ func (m *mockNode) SuggestedFee() basics.MicroAlgos { func (m *mockNode) Config() config.Local { return m.config } -func (m *mockNode) Start() {} - -func (m *mockNode) ListeningAddress() (string, bool) { - return "mock listening addresses not implemented", false -} - -func (m *mockNode) Stop() {} - -func (m *mockNode) ListTxns(addr basics.Address, minRound basics.Round, maxRound basics.Round) ([]node.TxnWithStatus, error) { - txns, ok := m.usertxns[addr] - if !ok { - return nil, fmt.Errorf("no txns for %s", addr) - } - - return txns, nil -} - -func (m *mockNode) GetTransaction(addr basics.Address, txID transactions.Txid, minRound basics.Round, maxRound basics.Round) (node.TxnWithStatus, bool) { - return node.TxnWithStatus{}, false -} - -func (m *mockNode) IsArchival() bool { - return false -} - -func (m *mockNode) OnNewBlock(block bookkeeping.Block, delta ledgercore.StateDelta) {} - -func (m *mockNode) Uint64() uint64 { - return 1 -} - -func (m *mockNode) GetTransactionByID(txid transactions.Txid, rnd basics.Round) (node.TxnWithStatus, error) { - return node.TxnWithStatus{}, fmt.Errorf("get transaction by id not implemented") -} - -func (m *mockNode) AssembleBlock(round basics.Round) (agreement.ValidatedBlock, error) { - return nil, fmt.Errorf("assemble block not implemented") -} func (m *mockNode) StartCatchup(catchpoint string) error { return m.err diff --git a/node/node.go b/node/node.go index 88766877dc..acf204facf 100644 --- a/node/node.go +++ b/node/node.go @@ -576,58 +576,6 @@ func (node *AlgorandFullNode) Simulate(request simulation.Request) (result simul return simulator.Simulate(request) } -// ListTxns returns SignedTxns associated with a specific account in a range of Rounds (inclusive). -// TxnWithStatus returns the round in which a particular transaction appeared, -// since that information is not part of the SignedTxn itself. -func (node *AlgorandFullNode) ListTxns(addr basics.Address, minRound basics.Round, maxRound basics.Round) ([]TxnWithStatus, error) { - result := make([]TxnWithStatus, 0) - for r := minRound; r <= maxRound; r++ { - h, err := node.ledger.AddressTxns(addr, r) - if err != nil { - return nil, err - } - for _, tx := range h { - result = append(result, TxnWithStatus{ - Txn: tx.SignedTxn, - ConfirmedRound: r, - ApplyData: tx.ApplyData, - }) - } - } - return result, nil -} - -// GetTransaction looks for the required txID within with a specific account within a range of rounds (inclusive) and -// returns the SignedTxn and true iff it finds the transaction. -func (node *AlgorandFullNode) GetTransaction(addr basics.Address, txID transactions.Txid, minRound basics.Round, maxRound basics.Round) (TxnWithStatus, bool) { - // start with the most recent round, and work backwards: - // this will abort early if it hits pruned rounds - if maxRound < minRound { - return TxnWithStatus{}, false - } - r := maxRound - for { - h, err := node.ledger.AddressTxns(addr, r) - if err != nil { - return TxnWithStatus{}, false - } - for _, tx := range h { - if tx.ID() == txID { - return TxnWithStatus{ - Txn: tx.SignedTxn, - ConfirmedRound: r, - ApplyData: tx.ApplyData, - }, true - } - } - if r == minRound { - break - } - r-- - } - return TxnWithStatus{}, false -} - // GetPendingTransaction looks for the required txID in the recent ledger // blocks, in the txpool, and in the txpool's status cache. It returns // the SignedTxn (with status information), and a bool to indicate if the @@ -1079,11 +1027,6 @@ func (node *AlgorandFullNode) txPoolGaugeThread(done <-chan struct{}) { } } -// IsArchival returns true the node is an archival node, false otherwise -func (node *AlgorandFullNode) IsArchival() bool { - return node.config.Archival -} - // OnNewBlock implements the BlockListener interface so we're notified after each block is written to the ledger func (node *AlgorandFullNode) OnNewBlock(block bookkeeping.Block, delta ledgercore.StateDelta) { if node.ledger.Latest() > block.Round() { @@ -1159,20 +1102,6 @@ func (node *AlgorandFullNode) Uint64() uint64 { return crypto.RandUint64() } -// GetTransactionByID gets transaction by ID -// this function is intended to be called externally via the REST api interface. -func (node *AlgorandFullNode) GetTransactionByID(txid transactions.Txid, rnd basics.Round) (TxnWithStatus, error) { - stx, _, err := node.ledger.LookupTxid(txid, rnd) - if err != nil { - return TxnWithStatus{}, err - } - return TxnWithStatus{ - Txn: stx.SignedTxn, - ConfirmedRound: rnd, - ApplyData: stx.ApplyData, - }, nil -} - // StartCatchup starts the catchpoint mode and attempt to get to the provided catchpoint // this function is intended to be called externally via the REST api interface. func (node *AlgorandFullNode) StartCatchup(catchpoint string) error { From 5f81b7bfc806d3064d404a3f047814003531d9d6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 26 Apr 2024 15:44:32 -0400 Subject: [PATCH 110/218] Testing: Allow go e2e tests to easily test multiple consensus versions (#5981) --- cmd/goal/network.go | 6 +++- netdeploy/network.go | 36 ++++++++++++++------ test/e2e-go/features/devmode/devmode_test.go | 22 ++++++------ test/framework/fixtures/fixture.go | 14 ++++++++ test/framework/fixtures/libgoalFixture.go | 16 ++++----- test/framework/fixtures/restClientFixture.go | 13 +++---- 6 files changed, 70 insertions(+), 37 deletions(-) diff --git a/cmd/goal/network.go b/cmd/goal/network.go index a0d8cf4241..41120f7db2 100644 --- a/cmd/goal/network.go +++ b/cmd/goal/network.go @@ -157,7 +157,11 @@ var networkCreateCmd = &cobra.Command{ consensus, _ = config.PreloadConfigurableConsensusProtocols(dataDir) } - network, err := netdeploy.CreateNetworkFromTemplate(networkName, networkRootDir, templateReader, binDir, !noImportKeys, nil, consensus, devModeOverride) + var overrides []netdeploy.TemplateOverride + if devModeOverride { + overrides = append(overrides, netdeploy.OverrideDevMode) + } + network, err := netdeploy.CreateNetworkFromTemplate(networkName, networkRootDir, templateReader, binDir, !noImportKeys, nil, consensus, overrides...) if err != nil { if noClean { reportInfof(" ** failed ** - Preserving network rootdir '%s'", networkRootDir) diff --git a/netdeploy/network.go b/netdeploy/network.go index d5e4f522ee..b26c8ef5bc 100644 --- a/netdeploy/network.go +++ b/netdeploy/network.go @@ -32,6 +32,7 @@ import ( "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/netdeploy/remote" "github.com/algorand/go-algorand/nodecontrol" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util" "golang.org/x/exp/maps" ) @@ -58,9 +59,27 @@ type Network struct { nodeExitCallback nodecontrol.AlgodExitErrorCallback } +// TemplateOverride is a function that modifies the NetworkTemplate after it is read in. +type TemplateOverride func(*NetworkTemplate) + +// OverrideDevMode turns on dev mode, regardless of whether the json says so. +func OverrideDevMode(template *NetworkTemplate) { + template.Genesis.DevMode = true + if len(template.Nodes) > 0 { + template.Nodes[0].IsRelay = false + } +} + +// OverrideConsensusVersion changes the protocol version of a template. +func OverrideConsensusVersion(ver protocol.ConsensusVersion) TemplateOverride { + return func(template *NetworkTemplate) { + template.Genesis.ConsensusProtocol = ver + } +} + // CreateNetworkFromTemplate uses the specified template to deploy a new private network // under the specified root directory. -func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, binDir string, importKeys bool, nodeExitCallback nodecontrol.AlgodExitErrorCallback, consensus config.ConsensusProtocols, overrideDevMode bool) (Network, error) { +func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, binDir string, importKeys bool, nodeExitCallback nodecontrol.AlgodExitErrorCallback, consensus config.ConsensusProtocols, overrides ...TemplateOverride) (Network, error) { n := Network{ rootDir: rootDir, nodeExitCallback: nodeExitCallback, @@ -70,19 +89,14 @@ func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, b var err error template := defaultNetworkTemplate - err = LoadTemplateFromReader(templateReader, &template) - - if err == nil { - if overrideDevMode { - template.Genesis.DevMode = true - if len(template.Nodes) > 0 { - template.Nodes[0].IsRelay = false - } - } - } else { + if err = LoadTemplateFromReader(templateReader, &template); err != nil { return n, err } + for _, overide := range overrides { + overide(&template) + } + if err = template.Validate(); err != nil { return n, err } diff --git a/test/e2e-go/features/devmode/devmode_test.go b/test/e2e-go/features/devmode/devmode_test.go index c2303085c1..525a6cacbd 100644 --- a/test/e2e-go/features/devmode/devmode_test.go +++ b/test/e2e-go/features/devmode/devmode_test.go @@ -26,20 +26,21 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/netdeploy" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" ) func TestDevMode(t *testing.T) { partitiontest.PartitionTest(t) + fixtures.MultiProtocolTest(t, testDevMode, protocol.ConsensusFuture, protocol.ConsensusCurrentVersion) +} - if testing.Short() { - t.Skip() - } - +func testDevMode(t *testing.T, version protocol.ConsensusVersion) { // Start devmode network, and make sure everything is primed by sending a transaction. var fixture fixtures.RestClientFixture - fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeNetwork.json")) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeNetwork.json"), netdeploy.OverrideConsensusVersion(version)) fixture.Start() defer fixture.Shutdown() sender, err := fixture.GetRichestAccount() @@ -76,17 +77,16 @@ func TestDevMode(t *testing.T) { } } -// Starts up a devmode network, sends a txn, and fetches the txn group delta for that txn func TestTxnGroupDeltasDevMode(t *testing.T) { partitiontest.PartitionTest(t) + fixtures.MultiProtocolTest(t, testTxnGroupDeltasDevMode, protocol.ConsensusFuture, protocol.ConsensusCurrentVersion) +} - if testing.Short() { - t.Skip() - } - +// Starts up a devmode network, sends a txn, and fetches the txn group delta for that txn +func testTxnGroupDeltasDevMode(t *testing.T, version protocol.ConsensusVersion) { // Start devmode network, and send a transaction. var fixture fixtures.RestClientFixture - fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeTxnTracerNetwork.json")) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeTxnTracerNetwork.json"), netdeploy.OverrideConsensusVersion(version)) fixture.Start() defer fixture.Shutdown() sender, err := fixture.GetRichestAccount() diff --git a/test/framework/fixtures/fixture.go b/test/framework/fixtures/fixture.go index a307534d3f..7e58b8eb33 100644 --- a/test/framework/fixtures/fixture.go +++ b/test/framework/fixtures/fixture.go @@ -19,6 +19,7 @@ package fixtures import ( "testing" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-deadlock" ) @@ -206,3 +207,16 @@ func (st *synchTest) Skipped() bool { defer st.Unlock() return st.t.Skipped() } + +// MultiProtocolTest runs a test for multiple consensus versions. It only runs +// against the *first* version when doing Short tests. +func MultiProtocolTest(t *testing.T, test func(t *testing.T, version protocol.ConsensusVersion), versions ...protocol.ConsensusVersion) { + for _, version := range versions { + t.Run(string(version), func(t *testing.T) { + test(t, version) + }) + if testing.Short() { + break // supply most important version first, probably future + } + } +} diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 1ffc6493c0..f1a13111a0 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -90,19 +90,19 @@ func (f *RestClientFixture) FasterConsensus(ver protocol.ConsensusVersion, timeo } // Setup is called to initialize the test fixture for the test(s) -func (f *LibGoalFixture) Setup(t TestingTB, templateFile string) { - f.setup(t, t.Name(), templateFile, true) +func (f *LibGoalFixture) Setup(t TestingTB, templateFile string, overrides ...netdeploy.TemplateOverride) { + f.setup(t, t.Name(), templateFile, true, overrides...) } // SetupNoStart is called to initialize the test fixture for the test(s) // but does not start the network before returning. Call NC.Start() to start later. -func (f *LibGoalFixture) SetupNoStart(t TestingTB, templateFile string) { - f.setup(t, t.Name(), templateFile, false) +func (f *LibGoalFixture) SetupNoStart(t TestingTB, templateFile string, overrides ...netdeploy.TemplateOverride) { + f.setup(t, t.Name(), templateFile, false, overrides...) } // SetupShared is called to initialize the test fixture that will be used for multiple tests -func (f *LibGoalFixture) SetupShared(testName string, templateFile string) { - f.setup(nil, testName, templateFile, true) +func (f *LibGoalFixture) SetupShared(testName string, templateFile string, overrides ...netdeploy.TemplateOverride) { + f.setup(nil, testName, templateFile, true, overrides...) } // Genesis returns the genesis data for this fixture @@ -110,7 +110,7 @@ func (f *LibGoalFixture) Genesis() gen.GenesisData { return f.network.Genesis() } -func (f *LibGoalFixture) setup(test TestingTB, testName string, templateFile string, startNetwork bool) { +func (f *LibGoalFixture) setup(test TestingTB, testName string, templateFile string, startNetwork bool, overrides ...netdeploy.TemplateOverride) { // Call initialize for our base implementation f.initialize(f) f.t = SynchronizedTest(test) @@ -122,7 +122,7 @@ func (f *LibGoalFixture) setup(test TestingTB, testName string, templateFile str importKeys := false // Don't automatically import root keys when creating folders, we'll import on-demand file, err := os.Open(templateFile) f.failOnError(err, "Template file could not be opened: %v") - network, err := netdeploy.CreateNetworkFromTemplate("test", f.rootDir, file, f.binDir, importKeys, f.nodeExitWithError, f.consensus, false) + network, err := netdeploy.CreateNetworkFromTemplate("test", f.rootDir, file, f.binDir, importKeys, f.nodeExitWithError, f.consensus, overrides...) f.failOnError(err, "CreateNetworkFromTemplate failed: %v") f.network = network diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 38e89c35e5..473df25d38 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/netdeploy" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/daemon/algod/api/client" @@ -44,20 +45,20 @@ type RestClientFixture struct { } // Setup is called to initialize the test fixture for the test(s) -func (f *RestClientFixture) Setup(t TestingTB, templateFile string) { - f.LibGoalFixture.Setup(t, templateFile) +func (f *RestClientFixture) Setup(t TestingTB, templateFile string, overrides ...netdeploy.TemplateOverride) { + f.LibGoalFixture.Setup(t, templateFile, overrides...) f.AlgodClient = f.GetAlgodClientForController(f.NC) } // SetupNoStart is called to initialize the test fixture for the test(s) // but does not start the network before returning. Call NC.Start() to start later. -func (f *RestClientFixture) SetupNoStart(t TestingTB, templateFile string) { - f.LibGoalFixture.SetupNoStart(t, templateFile) +func (f *RestClientFixture) SetupNoStart(t TestingTB, templateFile string, overrides ...netdeploy.TemplateOverride) { + f.LibGoalFixture.SetupNoStart(t, templateFile, overrides...) } // SetupShared is called to initialize the test fixture that will be used for multiple tests -func (f *RestClientFixture) SetupShared(testName string, templateFile string) { - f.LibGoalFixture.SetupShared(testName, templateFile) +func (f *RestClientFixture) SetupShared(testName string, templateFile string, overrides ...netdeploy.TemplateOverride) { + f.LibGoalFixture.SetupShared(testName, templateFile, overrides...) f.AlgodClient = f.GetAlgodClientForController(f.NC) } From d99c5c358548ebda6c79bf993f1626e305914f6a Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Tue, 30 Apr 2024 10:33:17 -0400 Subject: [PATCH 111/218] Tests: Fix flakey incentive e2e tests (#5986) --- .../features/incentives/payouts_test.go | 27 ++++++++++++++++--- .../features/incentives/suspension_test.go | 4 ++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/test/e2e-go/features/incentives/payouts_test.go b/test/e2e-go/features/incentives/payouts_test.go index 28fc91089e..7c4cc32aac 100644 --- a/test/e2e-go/features/incentives/payouts_test.go +++ b/test/e2e-go/features/incentives/payouts_test.go @@ -120,6 +120,7 @@ func TestBasicPayouts(t *testing.T) { } next, err := client.AccountData(block.Proposer().String()) + a.NoError(err) a.LessOrEqual(int(status.LastRound), int(next.LastProposed)) // regardless of proposer, nobody gets paid switch block.Proposer().String() { @@ -137,6 +138,12 @@ func TestBasicPayouts(t *testing.T) { a.NoError(err) } + // all nodes are in sync + for _, c := range []libgoal.Client{c15, c01, relay} { + _, err := c.WaitForRound(status.LastRound) + a.NoError(err) + } + // Wait until each have proposed, so we can see that 01 gets paid and 15 does not (too much balance) proposed01 := false proposed15 := false @@ -148,13 +155,17 @@ func TestBasicPayouts(t *testing.T) { a.EqualValues(bonus1, block.Bonus.Raw) next, err := client.AccountData(block.Proposer().String()) + a.NoError(err) fmt.Printf(" proposer %v has %d after proposing round %d\n", block.Proposer(), next.MicroAlgos.Raw, status.LastRound) // all nodes agree the proposer proposed for i, c := range []libgoal.Client{c15, c01, relay} { + _, err := c.WaitForRound(status.LastRound) + a.NoError(err) data, err := c.AccountData(block.Proposer().String()) a.NoError(err) - a.Equal(block.Round(), data.LastProposed, i) + // <= in case one node is behind, and the others have already advanced + a.LessOrEqual(block.Round(), data.LastProposed, i) } // 01 would get paid (because under balance cap) 15 would not @@ -211,14 +222,21 @@ func TestBasicPayouts(t *testing.T) { a.NoError(err) for _, c := range []libgoal.Client{c15, c01, relay} { + _, err := c.WaitForRound(status.LastRound) + a.NoError(err) data, err = c.AccountData(block.Proposer().String()) a.NoError(err) - a.Equal(block.Round(), data.LastProposed) - a.Equal(pdata, data) + // <= in case one node is behind, and the others have already advanced + a.LessOrEqual(block.Round(), data.LastProposed) + // <= in case one node is behind, and the others have already advanced + a.LessOrEqual(pdata.MicroAlgos.Raw, data.MicroAlgos.Raw) + a.Equal(pdata.Status, data.Status) + a.True(data.IncentiveEligible) data, err = c.AccountData(feesink.String()) a.NoError(err) - a.Equal(fdata, data) + // >= in case one node is behind, and the others have already advanced + a.GreaterOrEqual(fdata.MicroAlgos.Raw, data.MicroAlgos.Raw) } a.LessOrEqual(100000, int(data.MicroAlgos.Raw)) // won't go below minfee if data.MicroAlgos.Raw == 100000 { @@ -270,6 +288,7 @@ func TestBasicPayouts(t *testing.T) { // account is gone anyway (it didn't get paid) data, err = relay.AccountData(account01.Address) + a.NoError(err) a.Zero(data, "%+v", data) data, err = relay.AccountData(feesink.String()) diff --git a/test/e2e-go/features/incentives/suspension_test.go b/test/e2e-go/features/incentives/suspension_test.go index 3d3f0954f6..9be9cade6b 100644 --- a/test/e2e-go/features/incentives/suspension_test.go +++ b/test/e2e-go/features/incentives/suspension_test.go @@ -155,11 +155,13 @@ func TestBasicSuspension(t *testing.T) { stat, err = lg.WaitForRound(stat.LastRound + 1) a.NoError(err) attempts++ - a.Less(attempts, suspend20, "n20 didn't propose\n") + a.Less(attempts, 2*suspend20, "n20 didn't propose\n") } // paranoia. see payouts_test.go for more details. r := require.New(t) for i, c := range []libgoal.Client{c10, c20} { + _, err := c.WaitForRound(stat.LastRound) + r.NoError(err) account, err = c.AccountData(account20.Address) a.NoError(err) r.Equal(basics.Online, account.Status, i) From 13c50af606b8b118eb172340728d6384e76c495f Mon Sep 17 00:00:00 2001 From: Joe Polny <50534337+joe-p@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:00:38 -0400 Subject: [PATCH 112/218] api: blocks/{round}/logs endpoint (#5865) Co-authored-by: John Jannotti Co-authored-by: Jason Paulos --- daemon/algod/api/algod.oas2.json | 100 +++ daemon/algod/api/algod.oas3.yml | 135 ++++ daemon/algod/api/client/restClient.go | 6 + .../api/server/v2/generated/data/routes.go | 421 ++++++------ .../v2/generated/experimental/routes.go | 421 ++++++------ .../api/server/v2/generated/model/types.go | 17 + .../nonparticipating/private/routes.go | 427 +++++++------ .../nonparticipating/public/routes.go | 601 +++++++++--------- .../generated/participating/private/routes.go | 433 ++++++------- .../generated/participating/public/routes.go | 440 ++++++------- daemon/algod/api/server/v2/handlers.go | 64 ++ libgoal/libgoal.go | 9 + test/e2e-go/restAPI/other/appsRestAPI_test.go | 166 +++++ 13 files changed, 1897 insertions(+), 1343 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index b88daf40f1..9bd5e5c735 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -787,6 +787,62 @@ } ] }, + "/v2/blocks/{round}/logs": { + "get": { + "tags": [ + "public", + "nonparticipating" + ], + "description": "Get all of the logs from outer and inner app calls in the given round", + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Get all of the logs from outer and inner app calls in the given round", + "operationId": "GetBlockLogs", + "parameters": [ + { + "minimum": 0, + "type": "integer", + "description": "The round from which to fetch block log information.", + "name": "round", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/BlockLogsResponse" + }, + "400": { + "description": "Bad Request - Non integer number", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Nonexistent block ", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, "/v2/ledger/supply": { "get": { "tags": [ @@ -4461,6 +4517,33 @@ } } } + }, + "AppCallLogs": { + "description": "The logged messages from an app call along with the app ID and outer transaction ID. Logs appear in the same order that they were emitted.", + "type": "object", + "required": [ + "logs", + "application-index", + "txId" + ], + "properties": { + "logs": { + "description": "An array of logs", + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + }, + "application-index": { + "description": "The application from which the logs were generated", + "type": "integer" + }, + "txId": { + "description": "The transaction ID of the outer app call that lead to these logs", + "type": "string" + } + } } }, "parameters": { @@ -5118,6 +5201,23 @@ } } }, + "BlockLogsResponse": { + "description": "All logs emitted in the given round. Each app call, whether top-level or inner, that contains logs results in a separate AppCallLogs object. Therefore there may be multiple AppCallLogs with the same application ID and outer transaction ID in the event of multiple inner app calls to the same app. App calls with no logs are not included in the response. AppCallLogs are returned in the same order that their corresponding app call appeared in the block (pre-order traversal of inner app calls)", + "schema": { + "type": "object", + "required": [ + "logs" + ], + "properties": { + "logs": { + "type": "array", + "items": { + "$ref": "#/definitions/AppCallLogs" + } + } + } + } + }, "SupplyResponse": { "description": "Supply represents the current supply of MicroAlgos in the system.", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 96be615f79..ee29ce7176 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -323,6 +323,27 @@ }, "description": "Hash of a block header." }, + "BlockLogsResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "logs": { + "items": { + "$ref": "#/components/schemas/AppCallLogs" + }, + "type": "array" + } + }, + "required": [ + "logs" + ], + "type": "object" + } + } + }, + "description": "All logs emitted in the given round. Each app call, whether top-level or inner, that contains logs results in a separate AppCallLogs object. Therefore there may be multiple AppCallLogs with the same application ID and outer transaction ID in the event of multiple inner app calls to the same app. App calls with no logs are not included in the response. AppCallLogs are returned in the same order that their corresponding app call appeared in the block (pre-order traversal of inner app calls)" + }, "BlockResponse": { "content": { "application/json": { @@ -1215,6 +1236,34 @@ ], "type": "object" }, + "AppCallLogs": { + "description": "The logged messages from an app call along with the app ID and outer transaction ID. Logs appear in the same order that they were emitted.", + "properties": { + "application-index": { + "description": "The application from which the logs were generated", + "type": "integer" + }, + "logs": { + "description": "An array of logs", + "items": { + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "type": "array" + }, + "txId": { + "description": "The transaction ID of the outer app call that lead to these logs", + "type": "string" + } + }, + "required": [ + "application-index", + "logs", + "txId" + ], + "type": "object" + }, "Application": { "description": "Application index and its parameters", "properties": { @@ -3988,6 +4037,92 @@ ] } }, + "/v2/blocks/{round}/logs": { + "get": { + "description": "Get all of the logs from outer and inner app calls in the given round", + "operationId": "GetBlockLogs", + "parameters": [ + { + "description": "The round from which to fetch block log information.", + "in": "path", + "name": "round", + "required": true, + "schema": { + "minimum": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "logs": { + "items": { + "$ref": "#/components/schemas/AppCallLogs" + }, + "type": "array" + } + }, + "required": [ + "logs" + ], + "type": "object" + } + } + }, + "description": "All logs emitted in the given round. Each app call, whether top-level or inner, that contains logs results in a separate AppCallLogs object. Therefore there may be multiple AppCallLogs with the same application ID and outer transaction ID in the event of multiple inner app calls to the same app. App calls with no logs are not included in the response. AppCallLogs are returned in the same order that their corresponding app call appeared in the block (pre-order traversal of inner app calls)" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Bad Request - Non integer number" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Nonexistent block " + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + } + }, + "summary": "Get all of the logs from outer and inner app calls in the given round", + "tags": [ + "public", + "nonparticipating" + ] + } + }, "/v2/blocks/{round}/transactions/{txid}/proof": { "get": { "operationId": "GetTransactionProof", diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index f6494fa380..dc9be20543 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -776,3 +776,9 @@ func (client RestClient) GetBlockTimestampOffset() (response model.GetBlockTimeS err = client.get(&response, "/v2/devmode/blocks/offset", nil) return } + +// BlockLogs returns all the logs in a block for a given round +func (client RestClient) BlockLogs(round uint64) (response model.BlockLogsResponse, err error) { + err = client.get(&response, fmt.Sprintf("/v2/blocks/%d/logs", round), nil) + return +} diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index caabafbfd7..58b6e77fee 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -114,214 +114,219 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAAA////ElqSu/oAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index b0e581df69..7f2723f067 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -90,214 +90,219 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index 0ea0473989..435d296215 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -280,6 +280,18 @@ type AccountStateDelta struct { Delta StateDelta `json:"delta"` } +// AppCallLogs The logged messages from an app call along with the app ID and outer transaction ID. Logs appear in the same order that they were emitted. +type AppCallLogs struct { + // ApplicationIndex The application from which the logs were generated + ApplicationIndex uint64 `json:"application-index"` + + // Logs An array of logs + Logs [][]byte `json:"logs"` + + // TxId The transaction ID of the outer app call that lead to these logs + TxId string `json:"txId"` +} + // Application Application index and its parameters type Application struct { // Id \[appidx\] application index. @@ -1097,6 +1109,11 @@ type BlockHashResponse struct { BlockHash string `json:"blockHash"` } +// BlockLogsResponse defines model for BlockLogsResponse. +type BlockLogsResponse struct { + Logs []AppCallLogs `json:"logs"` +} + // BlockResponse defines model for BlockResponse. type BlockResponse struct { // Block Block header data. diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 38fa807a82..2ad021e012 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -139,217 +139,222 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAD//yBzl+wK/QAA", + "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", + "04A1VcMmT04eTT7+3wAAAP//lhM0FGMCAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index c89afa7516..8c43f44310 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -51,6 +51,9 @@ type ServerInterface interface { // Gets a proof for a given light block header inside a state proof commitment // (GET /v2/blocks/{round}/lightheader/proof) GetLightBlockHeaderProof(ctx echo.Context, round uint64) error + // Get all of the logs from outer and inner app calls in the given round + // (GET /v2/blocks/{round}/logs) + GetBlockLogs(ctx echo.Context, round uint64) error // Get a proof for a transaction in a block. // (GET /v2/blocks/{round}/transactions/{txid}/proof) GetTransactionProof(ctx echo.Context, round uint64, txid string, params GetTransactionProofParams) error @@ -363,6 +366,24 @@ func (w *ServerInterfaceWrapper) GetLightBlockHeaderProof(ctx echo.Context) erro return err } +// GetBlockLogs converts echo context to params. +func (w *ServerInterfaceWrapper) GetBlockLogs(ctx echo.Context) error { + var err error + // ------------- Path parameter "round" ------------- + var round uint64 + + err = runtime.BindStyledParameterWithLocation("simple", false, "round", runtime.ParamLocationPath, ctx.Param("round"), &round) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetBlockLogs(ctx, round) + return err +} + // GetTransactionProof converts echo context to params. func (w *ServerInterfaceWrapper) GetTransactionProof(ctx echo.Context) error { var err error @@ -702,6 +723,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/v2/blocks/:round", wrapper.GetBlock, m...) router.GET(baseURL+"/v2/blocks/:round/hash", wrapper.GetBlockHash, m...) router.GET(baseURL+"/v2/blocks/:round/lightheader/proof", wrapper.GetLightBlockHeaderProof, m...) + router.GET(baseURL+"/v2/blocks/:round/logs", wrapper.GetBlockLogs, m...) router.GET(baseURL+"/v2/blocks/:round/transactions/:txid/proof", wrapper.GetTransactionProof, m...) router.GET(baseURL+"/v2/blocks/:round/txids", wrapper.GetBlockTxids, m...) router.GET(baseURL+"/v2/deltas/txn/group/:id", wrapper.GetLedgerStateDeltaForTransactionGroup, m...) @@ -724,291 +746,300 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "9SMOgKoBAA==", + "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", + "V3SxYKoxIR49mT46evd/AwAA//+i458bR7YBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index ba583b859a..0d73a322b2 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -203,220 +203,225 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 8f2add21ae..518cbf482e 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,225 +177,231 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "PwAA//+yhC5iNxkBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 82bbbe6134..995363a491 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -735,6 +735,70 @@ func (v2 *Handlers) GetBlockTxids(ctx echo.Context, round uint64) error { return ctx.JSON(http.StatusOK, response) } +// NewAppCallLogs generates a new model.AppCallLogs struct. +func NewAppCallLogs(txid string, logs []string, appIndex uint64) model.AppCallLogs { + return model.AppCallLogs{ + TxId: txid, + Logs: convertSlice(logs, func(s string) []byte { return []byte(s) }), + ApplicationIndex: appIndex, + } +} + +func getAppIndexFromTxn(txn transactions.SignedTxnWithAD) uint64 { + appIndex := uint64(txn.SignedTxn.Txn.ApplicationID) + if appIndex == 0 { + appIndex = uint64(txn.ApplyData.ApplicationID) + } + + return appIndex +} + +func appendLogsFromTxns(blockLogs []model.AppCallLogs, txns []transactions.SignedTxnWithAD, outerTxnID string) []model.AppCallLogs { + + for _, txn := range txns { + if len(txn.EvalDelta.Logs) > 0 { + blockLogs = append( + blockLogs, + NewAppCallLogs(outerTxnID, txn.EvalDelta.Logs, getAppIndexFromTxn(txn)), + ) + } + + blockLogs = appendLogsFromTxns(blockLogs, txn.EvalDelta.InnerTxns, outerTxnID) + } + + return blockLogs +} + +// GetBlockLogs gets all of the logs (inner and outer app calls) for a given block +// (GET /v2/blocks/{round}/logs) +func (v2 *Handlers) GetBlockLogs(ctx echo.Context, round uint64) error { + ledger := v2.Node.LedgerForAPI() + block, err := ledger.Block(basics.Round(round)) + if err != nil { + switch err.(type) { + case ledgercore.ErrNoEntry: + return notFound(ctx, err, errFailedLookingUpLedger, v2.Log) + default: + return internalError(ctx, err, errFailedLookingUpLedger, v2.Log) + } + } + + txns, err := block.DecodePaysetFlat() + if err != nil { + return internalError(ctx, err, "decoding transactions", v2.Log) + } + + blockLogs := []model.AppCallLogs{} + + for _, txn := range txns { + blockLogs = appendLogsFromTxns(blockLogs, []transactions.SignedTxnWithAD{txn}, txn.ID().String()) + } + + response := model.BlockLogsResponse{Logs: blockLogs} + + return ctx.JSON(http.StatusOK, response) +} + // GetBlockHash gets the block hash for the given round. // (GET /v2/blocks/{round}/hash) func (v2 *Handlers) GetBlockHash(ctx echo.Context, round uint64) error { diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 75ddea8ab6..720e067019 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1339,3 +1339,12 @@ func (c *Client) GetLedgerStateDelta(round uint64) (rep model.LedgerStateDeltaRe } return } + +// BlockLogs returns all the logs in a block for a given round +func (c *Client) BlockLogs(round uint64) (resp model.BlockLogsResponse, err error) { + algod, err := c.ensureAlgodClient() + if err == nil { + return algod.BlockLogs(round) + } + return +} diff --git a/test/e2e-go/restAPI/other/appsRestAPI_test.go b/test/e2e-go/restAPI/other/appsRestAPI_test.go index c4a4986ed9..f270eb48a6 100644 --- a/test/e2e-go/restAPI/other/appsRestAPI_test.go +++ b/test/e2e-go/restAPI/other/appsRestAPI_test.go @@ -17,6 +17,7 @@ package other import ( + "encoding/base64" "encoding/binary" "encoding/hex" "fmt" @@ -549,3 +550,168 @@ end: assertBoxCount(numberOfBoxesRemaining) } + +func TestBlockLogs(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) + + innerTEAL := "#pragma version 10\nbyte 0xdeadbeef\nlog\nint 1" + + innerOps, err := logic.AssembleString(innerTEAL) + a.NoError(err) + innerApproval := innerOps.Program + a.NoError(err) + clearState := innerOps.Program + + b64InnerApproval := base64.StdEncoding.EncodeToString(innerApproval) + + outerTEAL := fmt.Sprintf(`#pragma version 10 + byte 0xDD0000DD + log + byte 0x + log + byte 0xDEADD00D + log + txn ApplicationID + bz ret + + itxn_begin + int appl + itxn_field TypeEnum + byte b64 %s + itxn_field ApprovalProgram + byte b64 %s + itxn_field ClearStateProgram + itxn_submit + + ret: + int 1 + return + `, b64InnerApproval, b64InnerApproval) + + outerOps, err := logic.AssembleString(outerTEAL) + a.NoError(err) + outerApproval := outerOps.Program + + gl := basics.StateSchema{} + lc := basics.StateSchema{} + + // create app + appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx( + 0, nil, nil, nil, + nil, nil, transactions.NoOpOC, + outerApproval, 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) + createConf, err := helper.WaitForTransaction(t, testClient, appCreateTxID, 30*time.Second) + a.NoError(err) + + createdAppID := basics.AppIndex(*createConf.ApplicationIndex) + + // 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() + payConf, err := helper.WaitForTransaction(t, testClient, appFundTxID.String(), 30*time.Second) + a.NoError(err) + + // get response when block has no app calls + resp, err := testClient.BlockLogs(*payConf.ConfirmedRound) + a.NoError(err) + expected := model.BlockLogsResponse{ + Logs: []model.AppCallLogs{}, + } + a.Equal(expected, resp) + + // call app twice + appCallTxn, err := testClient.MakeUnsignedAppNoOpTx( + uint64(createdAppID), nil, nil, nil, + nil, nil, + ) + a.NoError(err) + appCallTxn0, err := testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn) + a.NoError(err) + appCallTxn0.Note = []byte("0") + + appCallTxn1, err := testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn) + appCallTxn1.Note = []byte("1") + a.NoError(err) + + gid, err := testClient.GroupID([]transactions.Transaction{appCallTxn0, appCallTxn1}) + a.NoError(err) + appCallTxn0.Group = gid + appCallTxn1.Group = gid + + stxn0, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn0) + a.NoError(err) + stxn1, err := testClient.SignTransactionWithWallet(wh, nil, appCallTxn1) + a.NoError(err) + + err = testClient.BroadcastTransactionGroup([]transactions.SignedTxn{stxn0, stxn1}) + a.NoError(err) + + callConf, err := helper.WaitForTransaction(t, testClient, stxn0.ID().String(), 30*time.Second) + a.NoError(err) + + round := callConf.ConfirmedRound + + deadDood, err := hex.DecodeString("deadd00d") + a.NoError(err) + deadBeef, err := hex.DecodeString("deadbeef") + a.NoError(err) + dd0000dd, err := hex.DecodeString("dd0000dd") + a.NoError(err) + + // get block logs + resp, err = testClient.BlockLogs(*round) + a.NoError(err) + + expected = model.BlockLogsResponse{ + Logs: []model.AppCallLogs{ + { + ApplicationIndex: uint64(createdAppID), + TxId: stxn0.ID().String(), + Logs: [][]byte{dd0000dd, {}, deadDood}, + }, + { + ApplicationIndex: uint64(createdAppID + 3), + TxId: stxn0.ID().String(), + Logs: [][]byte{deadBeef}, + }, + { + ApplicationIndex: uint64(createdAppID), + TxId: stxn1.ID().String(), + Logs: [][]byte{dd0000dd, {}, deadDood}, + }, + { + ApplicationIndex: uint64(createdAppID + 5), + TxId: stxn1.ID().String(), + Logs: [][]byte{deadBeef}, + }, + }, + } + a.Equal(expected, resp) +} From ff2c966dfd735639d33b1c8dfaccfb267babb243 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:59:00 -0400 Subject: [PATCH 113/218] refactor: remove ledgercore.OnlineAccountData (#5965) --- ledger/acctdeltas_test.go | 6 +- ledger/acctonline.go | 34 ++++------- ledger/acctonline_test.go | 56 +++++++++---------- ledger/acctupdates_test.go | 2 +- ledger/ledger.go | 6 +- ledger/ledgercore/accountdata.go | 43 +++----------- ledger/store/trackerdb/data.go | 10 ++-- .../dualdriver/accounts_reader_ext.go | 2 +- .../generickv/accounts_ext_reader.go | 4 +- ledger/store/trackerdb/interface.go | 2 +- .../trackerdb/sqlitedriver/accountsV2.go | 4 +- 11 files changed, 62 insertions(+), 107 deletions(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index a3e5bd49db..2f78100ccd 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -2233,7 +2233,7 @@ func TestAccountOnlineQueries(t *testing.T) { MicroAlgos: basics.MicroAlgos{Raw: 100_000_000}, Status: basics.Online, }, - VotingData: ledgercore.VotingData{ + VotingData: basics.VotingData{ VoteID: voteIDA, }, } @@ -2243,7 +2243,7 @@ func TestAccountOnlineQueries(t *testing.T) { MicroAlgos: basics.MicroAlgos{Raw: 200_000_000}, Status: basics.Online, }, - VotingData: ledgercore.VotingData{ + VotingData: basics.VotingData{ VoteID: voteIDB, }, } @@ -2253,7 +2253,7 @@ func TestAccountOnlineQueries(t *testing.T) { MicroAlgos: basics.MicroAlgos{Raw: 300_000_000}, Status: basics.Online, }, - VotingData: ledgercore.VotingData{ + VotingData: basics.VotingData{ VoteID: voteIDC, }, } diff --git a/ledger/acctonline.go b/ledger/acctonline.go index f204681abe..667255fb51 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -618,21 +618,7 @@ func (ao *onlineAccounts) onlineTotals(rnd basics.Round) (basics.MicroAlgos, pro // LookupOnlineAccountData returns the online account data for a given address at a given round. func (ao *onlineAccounts) LookupOnlineAccountData(rnd basics.Round, addr basics.Address) (data basics.OnlineAccountData, err error) { - oad, err := ao.lookupOnlineAccountData(rnd, addr) - if err != nil { - return - } - - data.MicroAlgosWithRewards = oad.MicroAlgosWithRewards - data.VotingData.VoteID = oad.VotingData.VoteID - data.VotingData.SelectionID = oad.VotingData.SelectionID - data.VotingData.StateProofID = oad.VotingData.StateProofID - data.VotingData.VoteFirstValid = oad.VotingData.VoteFirstValid - data.VotingData.VoteLastValid = oad.VotingData.VoteLastValid - data.VotingData.VoteKeyDilution = oad.VotingData.VoteKeyDilution - data.IncentiveEligible = oad.IncentiveEligible - - return + return ao.lookupOnlineAccountData(rnd, addr) } // roundOffset calculates the offset of the given round compared to the current dbRound. Requires that the lock would be taken. @@ -676,7 +662,7 @@ func (ao *onlineAccounts) roundParamsOffset(rnd basics.Round) (offset uint64, er } // lookupOnlineAccountData returns the online account data for a given address at a given round. -func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics.Address) (ledgercore.OnlineAccountData, error) { +func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { needUnlock := false defer func() { if needUnlock { @@ -704,14 +690,14 @@ func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics. if err != nil { var roundOffsetError *RoundOffsetError if !errors.As(err, &roundOffsetError) { - return ledgercore.OnlineAccountData{}, err + return basics.OnlineAccountData{}, err } // the round number cannot be found in deltas, it is in history inHistory = true } paramsOffset, err = ao.roundParamsOffset(rnd) if err != nil { - return ledgercore.OnlineAccountData{}, err + return basics.OnlineAccountData{}, err } rewardsProto = config.Consensus[ao.onlineRoundParamsData[paramsOffset].CurrentProtocol] @@ -755,7 +741,7 @@ func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics. persistedData, err = ao.accountsq.LookupOnline(addr, rnd) if err != nil || persistedData.Ref == nil { // no such online account, return empty - return ledgercore.OnlineAccountData{}, err + return basics.OnlineAccountData{}, err } // Now we load the entire history of this account to fill the onlineAccountsCache, so that the // next lookup for this online account will not hit the on-disk DB. @@ -770,7 +756,7 @@ func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics. // 3. after postCommit => OK, postCommit does not add new entry with writeFrontIfExist, but here all the full history is loaded persistedDataHistory, validThrough, err := ao.accountsq.LookupOnlineHistory(addr) if err != nil || len(persistedDataHistory) == 0 { - return ledgercore.OnlineAccountData{}, err + return basics.OnlineAccountData{}, err } // 3. After we finished reading the history (lookupOnlineHistory), either // 1. The DB round has not advanced (validThrough == currentDbRound) => OK @@ -797,7 +783,7 @@ func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics. if !written { ao.accountsMu.Unlock() err = fmt.Errorf("failed to write history of acct %s for round %d into online accounts cache", data.Addr.String(), data.UpdRound) - return ledgercore.OnlineAccountData{}, err + return basics.OnlineAccountData{}, err } } ao.log.Info("inserted new item to onlineAccountsCache") @@ -810,7 +796,7 @@ func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics. if validThrough < currentDbRound { ao.log.Errorf("onlineAccounts.lookupOnlineAccountData: database round %d is behind in-memory round %d", validThrough, currentDbRound) - return ledgercore.OnlineAccountData{}, &StaleDatabaseRoundError{databaseRound: validThrough, memoryRound: currentDbRound} + return basics.OnlineAccountData{}, &StaleDatabaseRoundError{databaseRound: validThrough, memoryRound: currentDbRound} } } } @@ -1010,7 +996,7 @@ func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Rou } } -func (ao *onlineAccounts) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) (map[basics.Address]*ledgercore.OnlineAccountData, error) { +func (ao *onlineAccounts) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) (map[basics.Address]*basics.OnlineAccountData, error) { needUnlock := false defer func() { if needUnlock { @@ -1018,7 +1004,7 @@ func (ao *onlineAccounts) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) ( } }() - var expiredAccounts map[basics.Address]*ledgercore.OnlineAccountData + var expiredAccounts map[basics.Address]*basics.OnlineAccountData ao.accountsMu.RLock() needUnlock = true for { diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index 5a2b2ca1ac..d60353e2ae 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -207,7 +207,7 @@ func TestAcctOnline(t *testing.T) { var updates ledgercore.AccountDeltas acctIdx := int(i) - 1 - updates.Upsert(allAccts[acctIdx].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + updates.Upsert(allAccts[acctIdx].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) base := genesisAccts[i-1] newAccts := applyPartialDeltas(base, updates) @@ -489,13 +489,13 @@ func TestAcctOnlineCache(t *testing.T) { // put all accts online, then all offline, one each round if (int(i)-1)%(numAccts*2) >= numAccts { - updates.Upsert(allAccts[acctIdx].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + updates.Upsert(allAccts[acctIdx].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) } else { updates.Upsert(allAccts[acctIdx].Addr, ledgercore.ToAccountData(allAccts[acctIdx].AccountData)) } // set acctA online for each round - updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(100 * i)}}) + updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: basics.VotingData{VoteLastValid: basics.Round(100 * i)}}) base := genesisAccts[i-1] newAccts := applyPartialDeltas(base, updates) @@ -887,7 +887,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { require.NoError(t, err) var updates ledgercore.AccountDeltas - updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) // copy genesisAccts for the test accounts := copyGenesisAccts() @@ -967,7 +967,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { require.NoError(t, err) var updates ledgercore.AccountDeltas - updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) // copy genesisAccts for the test accounts := copyGenesisAccts() @@ -1016,8 +1016,8 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { addrB := ledgertesting.RandomAddress() var updates ledgercore.AccountDeltas - updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) - updates.Upsert(addrB, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: ledgercore.VotingData{VoteLastValid: 10000}}) + updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) + updates.Upsert(addrB, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: basics.VotingData{VoteLastValid: 10000}}) // copy genesisAccts for the test accounts := copyGenesisAccts() @@ -1174,9 +1174,9 @@ func TestAcctOnlineBaseAccountCache(t *testing.T) { accounts := genesisAccts acctDatas := [3]ledgercore.AccountData{ - {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(1000 + maxBalLookback)}}, - {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}, - {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(1000 + maxBalLookback)}}, + {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: basics.VotingData{VoteLastValid: basics.Round(1000 + maxBalLookback)}}, + {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}, + {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: basics.VotingData{VoteLastValid: basics.Round(1000 + maxBalLookback)}}, } // set online, offline, online for i := 1; i <= 3; i++ { @@ -1276,7 +1276,7 @@ func TestAcctOnlineVotersLongerHistory(t *testing.T) { maxBlocks := maxBalLookback * 5 for i := 1; i <= maxBlocks; i++ { var updates ledgercore.AccountDeltas - updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(100 * i)}}) + updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: basics.VotingData{VoteLastValid: basics.Round(100 * i)}}) base := genesisAccts[i-1] newAccts := applyPartialDeltas(base, updates) totals = newBlock(t, ml, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) @@ -1438,7 +1438,7 @@ func TestAcctOnlineTop(t *testing.T) { var updates ledgercore.AccountDeltas ac := allAccts[numAccts-3] updates.Upsert(ac.Addr, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: ac.MicroAlgos}, VotingData: ledgercore.VotingData{}}) + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: ac.MicroAlgos}, VotingData: basics.VotingData{}}) totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 1, oa) accountToBeUpdated := ac accountToBeUpdated.Status = basics.Offline @@ -1452,7 +1452,7 @@ func TestAcctOnlineTop(t *testing.T) { updates = ledgercore.AccountDeltas{} updates.Upsert(allAccts[numAccts-2].Addr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: allAccts[numAccts-2].MicroAlgos}, - VotingData: ledgercore.VotingData{ + VotingData: basics.VotingData{ VoteFirstValid: 0, VoteLastValid: 1, }}) @@ -1471,7 +1471,7 @@ func TestAcctOnlineTop(t *testing.T) { // mark an account with high stake as online - it should be pushed to the top of the list updates.Upsert(allAccts[numAccts-1].Addr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: allAccts[numAccts-1].MicroAlgos}, - VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(1000)}}) + VotingData: basics.VotingData{VoteLastValid: basics.Round(1000)}}) totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 3, oa) accountToBeUpdated = allAccts[numAccts-1] accountToBeUpdated.Status = basics.Online @@ -1549,7 +1549,7 @@ func TestAcctOnlineTopInBatches(t *testing.T) { if i == 300 { updates.Upsert(acct299.Addr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: acct299.MicroAlgos}, - VotingData: ledgercore.VotingData{}, + VotingData: basics.VotingData{}, }) } newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) @@ -1626,7 +1626,7 @@ func TestAcctOnlineTopBetweenCommitAndPostCommit(t *testing.T) { for ; i < 10; i++ { var updates ledgercore.AccountDeltas updates.Upsert(allAccts[numAccts-1].Addr, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) } @@ -1635,7 +1635,7 @@ func TestAcctOnlineTopBetweenCommitAndPostCommit(t *testing.T) { updateAccountsRoutine := func() { var updates ledgercore.AccountDeltas updates.Upsert(allAccts[numAccts-1].Addr, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) } @@ -1717,7 +1717,7 @@ func TestAcctOnlineTopDBBehindMemRound(t *testing.T) { for ; i < 10; i++ { var updates ledgercore.AccountDeltas updates.Upsert(allAccts[numAccts-1].Addr, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) } @@ -1726,7 +1726,7 @@ func TestAcctOnlineTopDBBehindMemRound(t *testing.T) { updateAccountsRoutine := func() { var updates ledgercore.AccountDeltas updates.Upsert(allAccts[numAccts-1].Addr, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: basics.VotingData{}}) newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) } @@ -1807,10 +1807,10 @@ func TestAcctOnlineTop_ChangeOnlineStake(t *testing.T) { var updates ledgercore.AccountDeltas if i == 15 { // round 15 should be in deltas (memory) // turn account `i` offline - updates.Upsert(allAccts[i].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: allAccts[i].MicroAlgos}, VotingData: ledgercore.VotingData{}}) + updates.Upsert(allAccts[i].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: allAccts[i].MicroAlgos}, VotingData: basics.VotingData{}}) } if i == 18 { - updates.Upsert(allAccts[i].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: allAccts[i].MicroAlgos}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(18)}}) + updates.Upsert(allAccts[i].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: allAccts[i].MicroAlgos}, VotingData: basics.VotingData{VoteLastValid: basics.Round(18)}}) } // else: insert empty block totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) } @@ -1969,19 +1969,19 @@ func TestAcctOnline_ExpiredOnlineCirculation(t *testing.T) { addrA := allAccts[0].Addr stakeA := allAccts[0].MicroAlgos statesA := map[acctState]ledgercore.AccountData{ - acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeA}, VotingData: ledgercore.VotingData{}}, - acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeA}, VotingData: ledgercore.VotingData(allAccts[0].OnlineAccountData().VotingData)}, + acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeA}, VotingData: basics.VotingData{}}, + acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeA}, VotingData: basics.VotingData(allAccts[0].OnlineAccountData().VotingData)}, } addrB := allAccts[1].Addr stakeB := allAccts[1].MicroAlgos votingDataB := allAccts[1].OnlineAccountData().VotingData statesB := map[acctState]ledgercore.AccountData{ - acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeB}, VotingData: ledgercore.VotingData{}}, - acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, VotingData: ledgercore.VotingData(votingDataB)}, + acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeB}, VotingData: basics.VotingData{}}, + acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, VotingData: basics.VotingData(votingDataB)}, } expStatesB := func(state acctState, voteRnd basics.Round) ledgercore.AccountData { - vd := ledgercore.VotingData(votingDataB) + vd := basics.VotingData(votingDataB) switch state { case acctStateExpired: vd.VoteLastValid = voteRnd - 1 @@ -2100,7 +2100,7 @@ func TestAcctOnline_ExpiredOnlineCirculation(t *testing.T) { commitSync(t, oa, ml, basics.Round(rnd-1)) a.Equal(int(conf.MaxAcctLookback), len(oa.deltas)) // ensure the only expected deltas are not flushed - var expiredAccts map[basics.Address]*ledgercore.OnlineAccountData + var expiredAccts map[basics.Address]*basics.OnlineAccountData err = ml.trackers.dbs.Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) error { reader, err := tx.MakeAccountsReader() if err != nil { @@ -2158,7 +2158,7 @@ func TestAcctOnline_ExpiredOnlineCirculation(t *testing.T) { base := accounts[rnd-1] updates.Upsert(addrA, statesA[acctStateOnline]) updates.Upsert(addrB, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, VotingData: ledgercore.VotingData(votingDataB), + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, VotingData: basics.VotingData(votingDataB), }) accounts = append(accounts, applyPartialDeltas(base, updates)) totals = newBlock(t, ml, proto, params, rnd, base, updates, totals) diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 34ffb2f32a..283fbcdc2e 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -429,7 +429,7 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base require.Equal(t, d, ledgercore.AccountData{}) od, err := ao.lookupOnlineAccountData(rnd, ledgertesting.RandomAddress()) require.NoError(t, err) - require.Equal(t, od, ledgercore.OnlineAccountData{}) + require.Equal(t, od, basics.OnlineAccountData{}) } } checkAcctUpdatesConsistency(t, au, latestRnd) diff --git a/ledger/ledger.go b/ledger/ledger.go index 110a72fc18..60cad748f7 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -621,11 +621,7 @@ func (l *Ledger) LookupAgreement(rnd basics.Round, addr basics.Address) (basics. // Intentionally apply (pending) rewards up to rnd. data, err := l.acctsOnline.LookupOnlineAccountData(rnd, addr) - if err != nil { - return basics.OnlineAccountData{}, err - } - - return data, nil + return data, err } // LookupWithoutRewards is like Lookup but does not apply pending rewards up diff --git a/ledger/ledgercore/accountdata.go b/ledger/ledgercore/accountdata.go index 92efa394a1..081fbffde6 100644 --- a/ledger/ledgercore/accountdata.go +++ b/ledger/ledgercore/accountdata.go @@ -18,8 +18,6 @@ package ledgercore 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/data/basics" ) @@ -29,7 +27,7 @@ import ( // separately, to better support on-disk and in-memory schemas that do not store them together. type AccountData struct { AccountBaseData - VotingData + basics.VotingData } // AccountBaseData contains base account info like balance, status and total number of resources @@ -54,24 +52,6 @@ type AccountBaseData struct { LastHeartbeat basics.Round // The last round that this account sent a heartbeat to show it was online. } -// VotingData holds participation information -type VotingData struct { - VoteID crypto.OneTimeSignatureVerifier - SelectionID crypto.VRFVerifier - StateProofID merklesignature.Commitment - - VoteFirstValid basics.Round - VoteLastValid basics.Round - VoteKeyDilution uint64 -} - -// OnlineAccountData holds MicroAlgosWithRewards and VotingData as needed for agreement -type OnlineAccountData struct { - MicroAlgosWithRewards basics.MicroAlgos - VotingData - IncentiveEligible bool -} - // ToAccountData returns ledgercore.AccountData from basics.AccountData func ToAccountData(acct basics.AccountData) AccountData { return AccountData{ @@ -95,7 +75,7 @@ func ToAccountData(acct basics.AccountData) AccountData { LastProposed: acct.LastProposed, LastHeartbeat: acct.LastHeartbeat, }, - VotingData: VotingData{ + VotingData: basics.VotingData{ VoteID: acct.VoteID, SelectionID: acct.SelectionID, StateProofID: acct.StateProofID, @@ -143,7 +123,7 @@ func (u AccountData) WithUpdatedRewards(proto config.ConsensusParams, rewardsLev // ClearOnlineState resets the account's fields to indicate that the account is an offline account func (u *AccountData) ClearOnlineState() { u.Status = basics.Offline - u.VotingData = VotingData{} + u.VotingData = basics.VotingData{} } // Suspend sets the status to Offline, but does _not_ clear voting keys, so @@ -194,26 +174,19 @@ func (u AccountData) Money(proto config.ConsensusParams, rewardsLevel uint64) (m } // OnlineAccountData calculates the online account data given an AccountData, by adding the rewards. -func (u AccountData) OnlineAccountData(proto config.ConsensusParams, rewardsLevel uint64) OnlineAccountData { +func (u AccountData) OnlineAccountData(proto config.ConsensusParams, rewardsLevel uint64) basics.OnlineAccountData { if u.Status != basics.Online { // if the account is not Online and agreement requests it for some reason, clear it out - return OnlineAccountData{} + return basics.OnlineAccountData{} } microAlgos, _, _ := basics.WithUpdatedRewards( proto, u.Status, u.MicroAlgos, u.RewardedMicroAlgos, u.RewardsBase, rewardsLevel, ) - return OnlineAccountData{ + return basics.OnlineAccountData{ MicroAlgosWithRewards: microAlgos, - VotingData: VotingData{ - VoteID: u.VoteID, - SelectionID: u.SelectionID, - StateProofID: u.StateProofID, - VoteFirstValid: u.VoteFirstValid, - VoteLastValid: u.VoteLastValid, - VoteKeyDilution: u.VoteKeyDilution, - }, - IncentiveEligible: u.IncentiveEligible, + VotingData: u.VotingData, + IncentiveEligible: u.IncentiveEligible, } } diff --git a/ledger/store/trackerdb/data.go b/ledger/store/trackerdb/data.go index da2cf3eeab..74e67e70b1 100644 --- a/ledger/store/trackerdb/data.go +++ b/ledger/store/trackerdb/data.go @@ -362,8 +362,8 @@ func (ba *BaseAccountData) GetLedgerCoreAccountBaseData() ledgercore.AccountBase } // GetLedgerCoreVotingData getter for voting data. -func (ba *BaseAccountData) GetLedgerCoreVotingData() ledgercore.VotingData { - return ledgercore.VotingData{ +func (ba *BaseAccountData) GetLedgerCoreVotingData() basics.VotingData { + return basics.VotingData{ VoteID: ba.VoteID, SelectionID: ba.SelectionID, StateProofID: ba.StateProofID, @@ -467,14 +467,14 @@ func (bo *BaseOnlineAccountData) GetOnlineAccount(addr basics.Address, normBalan // GetOnlineAccountData returns basics.OnlineAccountData for lookup agreement // TODO: unify with GetOnlineAccount/ledgercore.OnlineAccount -func (bo *BaseOnlineAccountData) GetOnlineAccountData(proto config.ConsensusParams, rewardsLevel uint64) ledgercore.OnlineAccountData { +func (bo *BaseOnlineAccountData) GetOnlineAccountData(proto config.ConsensusParams, rewardsLevel uint64) basics.OnlineAccountData { microAlgos, _, _ := basics.WithUpdatedRewards( proto, basics.Online, bo.MicroAlgos, basics.MicroAlgos{}, bo.RewardsBase, rewardsLevel, ) - return ledgercore.OnlineAccountData{ + return basics.OnlineAccountData{ MicroAlgosWithRewards: microAlgos, - VotingData: ledgercore.VotingData{ + VotingData: basics.VotingData{ VoteID: bo.VoteID, SelectionID: bo.SelectionID, StateProofID: bo.StateProofID, diff --git a/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go b/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go index 6f241d3e5e..6d6b527f48 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go +++ b/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go @@ -284,7 +284,7 @@ func (ar *accountsReaderExt) OnlineAccountsAll(maxAccounts uint64) (accounts []t } // ExpiredOnlineAccountsForRound implements trackerdb.AccountsReaderExt -func (ar *accountsReaderExt) ExpiredOnlineAccountsForRound(rnd basics.Round, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (expAccounts map[basics.Address]*ledgercore.OnlineAccountData, err error) { +func (ar *accountsReaderExt) ExpiredOnlineAccountsForRound(rnd basics.Round, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (expAccounts map[basics.Address]*basics.OnlineAccountData, err error) { expAccountsP, errP := ar.primary.ExpiredOnlineAccountsForRound(rnd, voteRnd, proto, rewardsLevel) expAccountsS, errS := ar.secondary.ExpiredOnlineAccountsForRound(rnd, voteRnd, proto, rewardsLevel) // coalesce errors diff --git a/ledger/store/trackerdb/generickv/accounts_ext_reader.go b/ledger/store/trackerdb/generickv/accounts_ext_reader.go index af2730a514..79460b51e7 100644 --- a/ledger/store/trackerdb/generickv/accounts_ext_reader.go +++ b/ledger/store/trackerdb/generickv/accounts_ext_reader.go @@ -353,7 +353,7 @@ func (r *accountsReader) OnlineAccountsAll(maxAccounts uint64) ([]trackerdb.Pers } // ExpiredOnlineAccountsForRound implements trackerdb.AccountsReaderExt -func (r *accountsReader) ExpiredOnlineAccountsForRound(rnd basics.Round, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (data map[basics.Address]*ledgercore.OnlineAccountData, err error) { +func (r *accountsReader) ExpiredOnlineAccountsForRound(rnd basics.Round, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (data map[basics.Address]*basics.OnlineAccountData, err error) { // The SQL at the time of writing: // // SELECT address, data, max(updround) @@ -364,7 +364,7 @@ func (r *accountsReader) ExpiredOnlineAccountsForRound(rnd basics.Round, voteRnd // ORDER BY address // initialize return map - data = make(map[basics.Address]*ledgercore.OnlineAccountData) + data = make(map[basics.Address]*basics.OnlineAccountData) expired := make(map[basics.Address]struct{}) // prepare iter over online accounts (by balance) diff --git a/ledger/store/trackerdb/interface.go b/ledger/store/trackerdb/interface.go index 8d88f0d42a..df9de98d17 100644 --- a/ledger/store/trackerdb/interface.go +++ b/ledger/store/trackerdb/interface.go @@ -129,7 +129,7 @@ type AccountsReaderExt interface { LookupOnlineAccountDataByAddress(addr basics.Address) (ref OnlineAccountRef, data []byte, err error) AccountsOnlineTop(rnd basics.Round, offset uint64, n uint64, proto config.ConsensusParams) (map[basics.Address]*ledgercore.OnlineAccount, error) AccountsOnlineRoundParams() (onlineRoundParamsData []ledgercore.OnlineRoundParamsData, endRound basics.Round, err error) - ExpiredOnlineAccountsForRound(rnd, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (map[basics.Address]*ledgercore.OnlineAccountData, error) + ExpiredOnlineAccountsForRound(rnd, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (map[basics.Address]*basics.OnlineAccountData, error) OnlineAccountsAll(maxAccounts uint64) ([]PersistedOnlineAccountData, error) LoadTxTail(ctx context.Context, dbRound basics.Round) (roundData []*TxTailRound, roundHash []crypto.Digest, baseRound basics.Round, err error) LoadAllFullAccounts(ctx context.Context, balancesTable string, resourcesTable string, acctCb func(basics.Address, basics.AccountData)) (count int, err error) diff --git a/ledger/store/trackerdb/sqlitedriver/accountsV2.go b/ledger/store/trackerdb/sqlitedriver/accountsV2.go index bb20285d9d..0ba84c84bd 100644 --- a/ledger/store/trackerdb/sqlitedriver/accountsV2.go +++ b/ledger/store/trackerdb/sqlitedriver/accountsV2.go @@ -303,7 +303,7 @@ func (r *accountsV2Reader) OnlineAccountsAll(maxAccounts uint64) ([]trackerdb.Pe } // ExpiredOnlineAccountsForRound returns all online accounts known at `rnd` that will be expired by `voteRnd`. -func (r *accountsV2Reader) ExpiredOnlineAccountsForRound(rnd, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (map[basics.Address]*ledgercore.OnlineAccountData, error) { +func (r *accountsV2Reader) ExpiredOnlineAccountsForRound(rnd, voteRnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (map[basics.Address]*basics.OnlineAccountData, error) { // This relies on SQLite's handling of max(updround) and bare columns not in the GROUP BY. // The values of votelastvalid, votefirstvalid, and data will all be from the same row as max(updround) rows, err := r.q.Query(`SELECT address, data, max(updround) @@ -317,7 +317,7 @@ ORDER BY address`, rnd, voteRnd) } defer rows.Close() - ret := make(map[basics.Address]*ledgercore.OnlineAccountData) + ret := make(map[basics.Address]*basics.OnlineAccountData) for rows.Next() { var addrbuf []byte var buf []byte From fe78417c1b9fa7ce680fcc57dca816c81f1e10d0 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Fri, 3 May 2024 15:24:23 -0400 Subject: [PATCH 114/218] API: Experimental full assets for account endpoint. (#5948) Co-authored-by: Jason Paulos Includes migration/addition of new ctype column on the resources table. --- cmd/goal/account.go | 79 +++ daemon/algod/api/algod.oas2.json | 109 ++++ daemon/algod/api/algod.oas3.yml | 148 +++++ daemon/algod/api/client/restClient.go | 19 + .../api/server/v2/generated/data/routes.go | 428 ++++++------- .../v2/generated/experimental/routes.go | 473 +++++++------- .../api/server/v2/generated/model/types.go | 37 ++ .../nonparticipating/private/routes.go | 384 ++++++------ .../nonparticipating/public/routes.go | 590 +++++++++--------- .../generated/participating/private/routes.go | 440 ++++++------- .../generated/participating/public/routes.go | 452 +++++++------- daemon/algod/api/server/v2/handlers.go | 105 ++++ .../server/v2/test/handlers_resources_test.go | 160 +++++ ledger/acctupdates.go | 53 ++ ledger/catchpointfilewriter_test.go | 6 +- ledger/ledger.go | 6 + ledger/ledger_test.go | 40 +- ledger/ledgercore/accountresource.go | 8 + ledger/store/trackerdb/data.go | 8 + .../trackerdb/dualdriver/accounts_reader.go | 36 ++ .../trackerdb/generickv/accounts_reader.go | 5 + .../store/trackerdb/generickv/migrations.go | 4 +- ledger/store/trackerdb/interface.go | 1 + ledger/store/trackerdb/sqlitedriver/schema.go | 96 +++ ledger/store/trackerdb/sqlitedriver/sql.go | 143 ++++- .../store/trackerdb/sqlitedriver/testing.go | 21 +- .../trackerdb/sqlitedriver/trackerdbV2.go | 17 + .../trackerdb/testsuite/accounts_kv_test.go | 198 ++++++ .../trackerdb/testsuite/sqlitedb_test.go | 3 + ledger/store/trackerdb/version.go | 2 +- libgoal/libgoal.go | 9 + test/scripts/e2e_subs/goal-account-asset.sh | 81 +++ 32 files changed, 2782 insertions(+), 1379 deletions(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 9fe230f670..c4ddd96fa2 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -71,6 +71,9 @@ var ( listAccountInfo bool onlyShowAssetIds bool partKeyIDToDelete string + + next string + limit uint64 ) func init() { @@ -79,6 +82,7 @@ func init() { accountCmd.AddCommand(listCmd) accountCmd.AddCommand(renameCmd) accountCmd.AddCommand(infoCmd) + accountCmd.AddCommand(assetDetailsCmd) accountCmd.AddCommand(balanceCmd) accountCmd.AddCommand(rewardsCmd) accountCmd.AddCommand(changeOnlineCmd) @@ -136,6 +140,12 @@ func init() { infoCmd.MarkFlagRequired("address") infoCmd.Flags().BoolVar(&onlyShowAssetIds, "onlyShowAssetIds", false, "Only show ASA IDs and not pull asset metadata") + // Asset details flags + assetDetailsCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to look up (required)") + assetDetailsCmd.MarkFlagRequired("address") + assetDetailsCmd.Flags().StringVarP(&next, "next", "n", "", "The next asset index to use for pagination") + assetDetailsCmd.Flags().Uint64VarP(&limit, "limit", "l", 0, "The maximum number of assets to return") + // Balance flags balanceCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to retrieve balance (required)") balanceCmd.MarkFlagRequired("address") @@ -539,6 +549,33 @@ var listCmd = &cobra.Command{ }, } +var assetDetailsCmd = &cobra.Command{ + Use: "assetdetails", + Short: "Retrieve information about the assets belonging to the specified account inclusive of asset metadata", + Long: `Retrieve information about the assets the specified account has created or opted into, inclusive of asset metadata.`, + Args: validateNoPosArgsFn, + Run: func(cmd *cobra.Command, args []string) { + dataDir := datadir.EnsureSingleDataDir() + client := ensureAlgodClient(dataDir) + + var nextPtr *string + var limitPtr *uint64 + if next != "" { + nextPtr = &next + } + if limit != 0 { + limitPtr = &limit + } + response, err := client.AccountAssetsInformation(accountAddress, nextPtr, limitPtr) + + if err != nil { + reportErrorf(errorRequestFail, err) + } + + printAccountAssetsInformation(accountAddress, response) + + }, +} var infoCmd = &cobra.Command{ Use: "info", Short: "Retrieve information about the assets and applications belonging to the specified account", @@ -731,6 +768,48 @@ func printAccountInfo(client libgoal.Client, address string, onlyShowAssetIds bo return hasError } +func printAccountAssetsInformation(address string, response model.AccountAssetsInformationResponse) { + fmt.Printf("Account: %s\n", address) + fmt.Printf("Round: %d\n", response.Round) + if response.NextToken != nil { + fmt.Printf("NextToken (to retrieve more account assets): %s\n", *response.NextToken) + } + fmt.Printf("Assets:\n") + for _, asset := range *response.AssetHoldings { + fmt.Printf(" Asset ID: %d\n", asset.AssetHolding.AssetID) + + if asset.AssetParams != nil { + amount := assetDecimalsFmt(asset.AssetHolding.Amount, asset.AssetParams.Decimals) + fmt.Printf(" Amount: %s\n", amount) + fmt.Printf(" IsFrozen: %t\n", asset.AssetHolding.IsFrozen) + fmt.Printf(" Asset Params:\n") + fmt.Printf(" Creator: %s\n", asset.AssetParams.Creator) + + name := "" + if asset.AssetParams.Name != nil { + _, name = unicodePrintable(*asset.AssetParams.Name) + } + fmt.Printf(" Name: %s\n", name) + + units := "units" + if asset.AssetParams.UnitName != nil { + _, units = unicodePrintable(*asset.AssetParams.UnitName) + } + fmt.Printf(" Units: %s\n", units) + fmt.Printf(" Total: %d\n", asset.AssetParams.Total) + fmt.Printf(" Decimals: %d\n", asset.AssetParams.Decimals) + safeURL := "" + if asset.AssetParams.Url != nil { + _, safeURL = unicodePrintable(*asset.AssetParams.Url) + } + fmt.Printf(" URL: %s\n", safeURL) + } else { + fmt.Printf(" Amount (without formatting): %d\n", asset.AssetHolding.Amount) + fmt.Printf(" IsFrozen: %t\n", asset.AssetHolding.IsFrozen) + } + } +} + var balanceCmd = &cobra.Command{ Use: "balance", Short: "Retrieve the balances for the specified account", diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 9bd5e5c735..704b8a4638 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -344,6 +344,73 @@ } ] }, + "/v2/accounts/{address}/assets": { + "get": { + "description": "Lookup an account's asset holdings.", + "tags": [ + "public", + "experimental" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Get a list of assets held by an account, inclusive of asset params.", + "operationId": "AccountAssetsInformation", + "parameters": [ + { + "pattern": "[A-Z0-9]{58}", + "type": "string", + "description": "An account public key", + "name": "address", + "in": "path", + "required": true + }, + { + "$ref": "#/parameters/limit" + }, + { + "$ref": "#/parameters/next" + } + ], + "responses": { + "200": { + "$ref": "#/responses/AccountAssetsInformationResponse" + }, + "400": { + "description": "Malformed address", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + }, + "parameters": [ + { + "type": "string", + "name": "address", + "in": "path", + "required": true + } + ] + }, "/v2/accounts/{address}/applications/{application-id}": { "get": { "description": "Given a specific account public key and application ID, this call returns the account's application local state and global state (AppLocalState and AppParams, if either exists). Global state will only be returned if the provided address is the application's creator.", @@ -3087,6 +3154,23 @@ } } }, + "AccountAssetHolding": { + "description": "AccountAssetHolding describes the account's asset holding and asset parameters (if either exist) for a specific asset ID.", + "type": "object", + "required": [ + "asset-holding" + ], + "properties": { + "asset-holding": { + "description": "\\[asset\\] Details about the asset held by this account.\n\nThe raw account uses `AssetHolding` for this type.", + "$ref": "#/definitions/AssetHolding" + }, + "asset-params": { + "description": "\\[apar\\] parameters of the asset held by this account.\n\nThe raw account uses `AssetParams` for this type.", + "$ref": "#/definitions/AssetParams" + } + } + }, "AccountParticipation": { "description": "AccountParticipation describes the parameters used by this account in consensus protocol.", "type": "object", @@ -4818,6 +4902,31 @@ } } }, + "AccountAssetsInformationResponse": { + "description": "AccountAssetsInformationResponse contains a list of assets held by an account.", + "schema": { + "type": "object", + "required": [ + "round" + ], + "properties": { + "round": { + "description": "The round for which this information is relevant.", + "type": "integer" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter.", + "type": "string" + }, + "asset-holdings": { + "type": "array", + "items": { + "$ref": "#/definitions/AccountAssetHolding" + } + } + } + } + }, "AccountApplicationResponse": { "description": "AccountApplicationResponse describes the account's application local state and global state (AppLocalState and AppParams, if either exists) for a specific application ID. Global state will only be returned if the provided address is the application's creator.", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index ee29ce7176..734dc4667a 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -274,6 +274,35 @@ }, "description": "AccountAssetResponse describes the account's asset holding and asset parameters (if either exist) for a specific asset ID. Asset parameters will only be returned if the provided address is the asset's creator." }, + "AccountAssetsInformationResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "asset-holdings": { + "items": { + "$ref": "#/components/schemas/AccountAssetHolding" + }, + "type": "array" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter.", + "type": "string" + }, + "round": { + "description": "The round for which this information is relevant.", + "type": "integer" + } + }, + "required": [ + "round" + ], + "type": "object" + } + } + }, + "description": "AccountAssetsInformationResponse contains a list of assets held by an account." + }, "AccountResponse": { "content": { "application/json": { @@ -1177,6 +1206,21 @@ ], "type": "object" }, + "AccountAssetHolding": { + "description": "AccountAssetHolding describes the account's asset holding and asset parameters (if either exist) for a specific asset ID.", + "properties": { + "asset-holding": { + "$ref": "#/components/schemas/AssetHolding" + }, + "asset-params": { + "$ref": "#/components/schemas/AssetParams" + } + }, + "required": [ + "asset-holding" + ], + "type": "object" + }, "AccountParticipation": { "description": "AccountParticipation describes the parameters used by this account in consensus protocol.", "properties": { @@ -3079,6 +3123,110 @@ ] } }, + "/v2/accounts/{address}/assets": { + "get": { + "description": "Lookup an account's asset holdings.", + "operationId": "AccountAssetsInformation", + "parameters": [ + { + "description": "An account public key", + "in": "path", + "name": "address", + "required": true, + "schema": { + "pattern": "[A-Z0-9]{58}", + "type": "string" + } + }, + { + "description": "Maximum number of results to return.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "description": "The next page of results. Use the next token provided by the previous results.", + "in": "query", + "name": "next", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "asset-holdings": { + "items": { + "$ref": "#/components/schemas/AccountAssetHolding" + }, + "type": "array" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter.", + "type": "string" + }, + "round": { + "description": "The round for which this information is relevant.", + "type": "integer" + } + }, + "required": [ + "round" + ], + "type": "object" + } + } + }, + "description": "AccountAssetsInformationResponse contains a list of assets held by an account." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Malformed address" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Get a list of assets held by an account, inclusive of asset params.", + "tags": [ + "public", + "experimental" + ] + } + }, "/v2/accounts/{address}/assets/{asset-id}": { "get": { "description": "Given a specific account public key and asset ID, this call returns the account's asset holding and asset parameters (if either exist). Asset parameters will only be returned if the provided address is the asset's creator.", diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index dc9be20543..33b8da8ee1 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -377,6 +377,11 @@ type accountInformationParams struct { Exclude string `url:"exclude"` } +type pageParams struct { + Next *string `url:"next,omitempty"` + Limit *uint64 `url:"limit,omitempty"` +} + type catchupParams struct { Min uint64 `url:"min"` } @@ -508,6 +513,20 @@ func (client RestClient) RawAccountAssetInformation(accountAddress string, asset return } +// AccountAssetsInformation gets account information about a particular account's assets, subject to pagination. +func (client RestClient) AccountAssetsInformation(accountAddress string, next *string, limit *uint64) (response model.AccountAssetsInformationResponse, err error) { + err = client.get(&response, fmt.Sprintf("/v2/accounts/%s/assets", accountAddress), pageParams{next, limit}) + return +} + +// RawAccountAssetsInformation gets account information about a particular account's assets, subject to pagination. +func (client RestClient) RawAccountAssetsInformation(accountAddress string, next *string, limit *uint64) (response []byte, err error) { + var blob Blob + err = client.getRaw(&blob, fmt.Sprintf("/v2/accounts/%s/assets", accountAddress), pageParams{next, limit}) + response = blob + return +} + // SuggestedParams gets the suggested transaction parameters func (client RestClient) SuggestedParams() (response model.TransactionParametersResponse, err error) { err = client.get(&response, "/v2/transactions/params", nil) diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 58b6e77fee..7f5cfbeb7a 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -114,219 +114,221 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "y8xbNdpOULPHJw9nH/93AAAA//8Oc6QNuAMBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index 7f2723f067..e6472ca97a 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -8,17 +8,22 @@ import ( "compress/gzip" "encoding/base64" "fmt" + "net/http" "net/url" "path" "strings" . "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" + "github.com/algorand/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" ) // ServerInterface represents all server handlers. type ServerInterface interface { + // Get a list of assets held by an account, inclusive of asset params. + // (GET /v2/accounts/{address}/assets) + AccountAssetsInformation(ctx echo.Context, address string, params AccountAssetsInformationParams) error // Returns OK if experimental API is enabled. // (GET /v2/experimental) ExperimentalCheck(ctx echo.Context) error @@ -32,6 +37,40 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// AccountAssetsInformation converts echo context to params. +func (w *ServerInterfaceWrapper) AccountAssetsInformation(ctx echo.Context) error { + var err error + // ------------- Path parameter "address" ------------- + var address string + + err = runtime.BindStyledParameterWithLocation("simple", false, "address", runtime.ParamLocationPath, ctx.Param("address"), &address) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter address: %s", err)) + } + + ctx.Set(Api_keyScopes, []string{""}) + + // Parameter object where we will unmarshal all parameters from the context + var params AccountAssetsInformationParams + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "next" ------------- + + err = runtime.BindQueryParameter("form", true, false, "next", ctx.QueryParams(), ¶ms.Next) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter next: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.AccountAssetsInformation(ctx, address, params) + return err +} + // ExperimentalCheck converts echo context to params. func (w *ServerInterfaceWrapper) ExperimentalCheck(ctx echo.Context) error { var err error @@ -82,6 +121,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.GET(baseURL+"/v2/accounts/:address/assets", wrapper.AccountAssetsInformation, m...) router.GET(baseURL+"/v2/experimental", wrapper.ExperimentalCheck, m...) router.POST(baseURL+"/v2/transactions/async", wrapper.RawTransactionAsync, m...) @@ -90,219 +130,226 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9/5PbNrIg/q+g9F6VY3/EGX9L3saf2no3sZPsXJzE5XGy957tSyCyJWGHArgAOCPF", - "5//9Ct0ACZKgRM1MnGzV/WSPSAKNRqPR3/vDLFebSkmQ1syefZhVXPMNWND4F89zVUubicL9VYDJtais", - "UHL2LDxjxmohV7P5TLhfK27Xs/lM8g2077jv5zMN/6yFhmL2zOoa5jOTr2HD3cB2V7m3m5G22Uplfogz", - "GuL8xezjnge8KDQYM4TyR1numJB5WRfArObS8Nw9Muxa2DWza2GY/5gJyZQEppbMrjsvs6WAsjAnYZH/", - "rEHvolX6yceX9LEFMdOqhCGcz9VmISQEqKABqtkQZhUrYIkvrbllbgYHa3jRKmaA63zNlkofAJWAiOEF", - "WW9mz97ODMgCNO5WDuIK/7vUAL9BZrlegZ29n6cWt7SgMys2iaWde+xrMHVpDcN3cY0rcQWSua9O2Pe1", - "sWwBjEv2+pvn7MmTJ1+6hWy4tVB4IhtdVTt7vCb6fPZsVnAL4fGQ1ni5UprLImvef/3Nc5z/wi9w6lvc", - "GEgfljP3hJ2/GFtA+DBBQkJaWOE+dKjffZE4FO3PC1gqDRP3hF6+002J5/9DdyXnNl9XSkib2BeGTxk9", - "TvKw6PN9PKwBoPN+5TCl3aBvH2Zfvv/waP7o4cd/e3uW/bf/8/MnHycu/3kz7gEMJF/Ma61B5rtspYHj", - "aVlzOcTHa08PZq3qsmBrfoWbzzfI6v23zH1LrPOKl7WjE5FrdVaulGHck1EBS16XloWJWS1Lx6bcaJ7a", - "mTCs0upKFFDMHfe9Xot8zXJuaAh8j12LsnQ0WBsoxmgtvbo9h+ljjBIH143wgQv68yKjXdcBTMAWuUGW", - "l8pAZtWB6yncOFwWLL5Q2rvKHHdZsTdrYDi5e0CXLeJOOpouyx2zuK8F44ZxFq6mORNLtlM1u8bNKcUl", - "fu9X47C2YQ5puDmde9Qd3jH0DZCRQN5CqRK4ROSFczdEmVyKVa3BsOs12LW/8zSYSkkDTC3+Abl12/4/", - "L378gSnNvgdj+Ape8fySgcxVAcUJO18yqWxEGp6WEIfuy7F1eLhSl/w/jHI0sTGriueX6Ru9FBuRWNX3", - "fCs29YbJerMA7bY0XCFWMQ221nIMIBrxAClu+HY46Rtdyxz3v522I8s5ahOmKvkOEbbh278+nHtwDONl", - "ySqQhZArZrdyVI5zcx8GL9OqlsUEMce6PY0uVlNBLpYCCtaMsgcSP80heIQ8Dp5W+IrACYOMgtPMcgAc", - "CdsEzbjT7Z6wiq8gIpkT9pNnbvjUqkuQDaGzxQ4fVRquhKpN89EIjDj1fglcKgtZpWEpEjR24dHhGAy9", - "4znwxstAuZKWCwmFY84ItLJAzGoUpmjC/frO8BZfcANfPB2749unE3d/qfq7vnfHJ+02vpTRkUxcne6p", - "P7Bpyarz/QT9MJ7biFVGPw82UqzeuNtmKUq8if7h9i+goTbIBDqICHeTESvJba3h2Tv5wP3FMnZhuSy4", - "LtwvG/rp+7q04kKs3E8l/fRSrUR+IVYjyGxgTSpc+NmG/nHjpdmx3Sb1ipdKXdZVvKC8o7guduz8xdgm", - "05jHEuZZo+3GisebbVBGjv3CbpuNHAFyFHcVdy9ewk6Dg5bnS/xnu0R64kv9m/unqkr3ta2WKdQ6OvZX", - "MpoPvFnhrKpKkXOHxNf+sXvqmACQIsHbN07xQn32IQKx0qoCbQUNyqsqK1XOy8xYbnGkf9ewnD2b/dtp", - "a385pc/NaTT5S/fVBX7kRFYSgzJeVUeM8cqJPmYPs3AMGh8hmyC2h0KTkLSJjpSEY8ElXHFpT1qVpcMP", - "mgP81s/U4pukHcJ3TwUbRTijFxdgSAKmF+8ZFqGeIVoZohUF0lWpFs0Pn51VVYtBfH5WVYQPlB5BoGAG", - "W2GsuY/L5+1Jiuc5f3HCvo3HRlFcyXLnLgcSNdzdsPS3lr/FGtuSX0M74j3DcDuVPnFbE9DgxPy7oDhU", - "K9aqdFLPQVpxL//NvxuTmft90sf/GiQW43acuFDR8pgjHQd/iZSbz3qUMyQcb+45YWf9b29GNm6UNMHc", - "iFb27ieNuwePDQqvNa8IQP+E7lIhUUmjlwjWW3LTiYwuCXN0hiNaQ6hufNYOnockJEgKPRi+KlV++Tdu", - "1ndw5hdhrOHxw2nYGngBmq25WZ/MUlJGfLza0aYcMfciKvhsEU110izxpVqZO1hiqVb4r7CwMRMo4zkv", - "Szc1Chq0Bq413w1WiwNP4iVlydzLDDYCraBeGyCzKQnV7Guerx2vZzkvy3mr/6sqK+EKSqeJCSlBz5ld", - "cxt0C0MjB2EVz5EBxzwssGg13naAdhPdKJga2IYjW9k4EbUqu980ngXDN9C72pDNqRpVw0h6PH8RVgdX", - "IK3b32ZoBL9ZI6rg8eAnbm7/CGeWihZHZh0bfDIN/hp+0QHavd0ySdlOoXRBhkjrfhOa5UrTEMS2/eTu", - "P8B1+zFR52eVhswPofkVaMNLt7reou435HtXp/PAySy45dHJ9FSYlqqJc+B3eGeDTqjeP+J/eMncY3c1", - "OUpqqUfgDaMiH1nhiJVQRTO5F9CIptiG7FOs4vnlUVA+bydPs5lJJ+9rMon5LfSLaHbozVYU5q62CQcb", - "26vuCSGDRGBHPZZ6gOlEc01BwBtVMWIfPRCIU+BohBC1vfNr7Su1TcH0ldoOrjS1hTvZCTfOZGb/ldq+", - "8JApfRjzOPYUpLsFOlXU4O0mY8bpZmmdLWcLpW8mTfQuGMlaFxLjbtRImJr3kISv1lXmz2bCDE0v9AZq", - "vfb7hYD+8CmMdbBwYfnvgAXjRr0LLHQHumssqE0lSrgD0l8nhbgFN/DkMbv429nnjx7/8vjzLxxJVlqt", - "NN+wxc6CYZ95WwszdlfC/eHK0NpRlzY9+hdPg+OhO25qHKNqncOGV8OhyKFBKg29xtx7Q6x10YyrbgCc", - "xBHBXW2Edka+OgfaC2GcxrRZ3MlmjCGsaGcpmIekgIPEdOzy2ml28RL1Ttd3YZoCrZVOXl2VVlblqsyc", - "fCRUwjv6yr/B/BtBXa36vxO07Job5uZGV04tUUFIUJbdyul8n4Z+s5UtbvZyflpvYnV+3in70kV+K71X", - "oDO7layARb3qWDaWWm0YZwV+iHf0t2BJbhEbuLB8U/24XN6N6UfhQAkTjNiAcTMxesNJDQZyJSmy6YC1", - "xY86BT19xASTux0HwGPkYidz9BvcxbEdN0RthEQnptnJPLJKORhLKFYdsry99WkMHTTVPZMAx6HjJT5G", - "w+ULKC3/Ruk3rdj3rVZ1dedCXn/OqcvhfjHeNFq4b4NNTMhV2Y2mWznYT1Jr/EMW9LxRvmkNCD1S5Eux", - "WttIz3qllVrePYypWVKA4gMyspTum6Gp5QdVOGZia3MHIlg7WMvhHN3GfI0vVG0ZZ1IVgJtfm7RwNhJ/", - "hYEfGK9iY3kP9Xph2AIcdeW8dqutK4bRGIP7ov0w4zmd0AxRY0Z80U0QAb1F01FsT6mBFzu2AJBMLbzD", - "17uicZEcQ0lsEG+8aJjgFx24Kq1yMAaKzBuaD4IW3qOrw+7BEwKOADezMKPYkutbA3t5dRDOS9hlGPhk", - "2Gff/Wzu/wHwWmV5eQCx+E4KvX071BDqadPvI7j+5DHZkYWLqJZZhdJsCRbGUHgUTkb3rw/RYBdvj5Yr", - "0Ohf/10pPkxyOwJqQP2d6f220NbVSDivV2+dhOc2THKpgmCVGqzkxmaH2LJ7qaODuxVEnDDFiXHgEcHr", - "JTeWYkKELNAWSNcJzkNCmJtiHOBRNcSN/HPQQIZj5+4elKY2jTpi6qpS2kKRWoOE7Z65foBtM5daRmM3", - "Oo9VrDZwaOQxLEXje2TRSghB3DauUx80NVwcOhjdPb9LorIDRIuIfYBchLci7MYhjSOACNMimghHmB7l", - "NHGU85mxqqoct7BZLZvvxtB0QW+f2Z/ad4fERc4BurcLBQYdD/59D/k1YZaCWdfcMA8H2/BLJ3ugGYSC", - "V4Ywu8OYGSFzyPZRPqp47q34CBw8pHW10ryArICS74aD/kSPGT3eNwDueKvuKgsZRSWmN72l5BAEtmdo", - "heOZlPDI8AnL3RF0qkBLIP7rAyMXgGOnmJOno3vNUDhXcovCeLhs2urEiHgbXinrdtzTA4LsOfoUgEfw", - "0Ax9c1Tgx1mre/an+C8wfoJGjjh+kh2YsSW04x+1gBEbqk/4iM5Lj733OHCSbY6ysQN8ZOzIjhh0X3Ft", - "RS4q1HW+g92dq379CZIOZ1aA5aKEgkUPSA2s4u8ZxdP1x7yZKjjJ9jYEf2B8SyynFAZFni7wl7BDnfsV", - "BWpHpo670GUTo7r7iUuGgIbwTyeCx6/Alue23DlBza5hx65BAzP1glz/Qz+EVVUWD5D0a+yZ0Xs1kz7F", - "vW7WCxwqWt5wK+Yz0gn2w/empxh00OF1gUqpcoKFbICMJASTYi5YpdyuC58LErIBAiV1gPRMG13azfV/", - "z3TQjCtg/6VqlnOJKldtoZFplEZBAQVIN4MTwZo5faRWiyEoYQOkSeKTBw/6C3/wwO+5MGwJ1yGByr3Y", - "R8eDB2jHeaWM7RyuO7CHuuN2nrg+0OHjLj6vhfR5yuFIIT/ylJ181Ru88RK5M2WMJ1y3/FszgN7J3E5Z", - "e0wj06KkcNxJvpxuXM1g3bjvF2JTl9zehdcKrniZqSvQWhRwkJP7iYWSX1/x8sfmM0wOg9zRaA5ZjilN", - "E8eCN+4byoJy4wgp3AGmCOipAME5fXVBHx1QMdsgU7HZQCG4hXLHKg05UPKPkxxNs9QTRmHB+ZrLFSoM", - "WtUrH5dK4yDDrw2ZZnQtB0MkhSq7lRkauVMXgA/vCvlfTpwC7lS6voWcFJhr3sznU/6m3MzRHvQ9Bkkn", - "2Xw2qvE6pF61Gi8hp5vENuEy6Mh7EX7aiSe6UhB1TvYZ4iveFneY3Ob+Pib7dugUlMOJo0jZ9uFYsKxT", - "t8vdHQg9NBDTUGkweEXFZipDT9UyTlgNIXY7Y2EztOTTp7+MHL/Xo/qikqWQkG2UhF2yRoOQ8D0+TB4n", - "vCZHPkaBZezbvg7Sgb8HVneeKdR4W/zibvdPaN9jZb5R+q5cojTgZPF+ggfyoLvdT3lTPykvy4Rr0aez", - "9RmAmTdBrkIzbozKBcps54WZ+2ha8kb63Lcu+l81Qfp3cPb64/Z8aHGmNNqIoawYZ3kp0IKspLG6zu07", - "ydFGFS01EfwUlPFxq+Xz8EraTJqwYvqh3kmOgW+N5SoZsLGEhJnmG4BgvDT1agXG9nSdJcA76d8SktVS", - "WJxr445LRuelAo0RSCf05obv2NLRhFXsN9CKLWrblf4xW9NYUZbeoeemYWr5TnLLSuDGsu+FfLPF4YLT", - "PxxZCfZa6csGC+nbfQUSjDBZOkjrW3qK8fB++WsfG49h4vQ4BGu26eMzt8xOxYj//dl/Pnt7lv03z357", - "mH35/52+//D04/0Hgx8ff/zrX/9P96cnH/96/z//PbVTAfZULqGH/PyF14zPX6D6E4W492H/ZPb/jZBZ", - "ksjiaI4ebbHPMG/eE9D9rnHMruGdtFvpCOmKl6JwvOUm5NC/YQZnkU5Hj2o6G9EzhoW1HqlU3ILLsAST", - "6bHGG0tRw7jGdNYuOiV9Ii6el2UtaSuD9E1JaSG+TC3nTWY2FW16xjBtd81DcKT/8/HnX8zmbbpt83w2", - "n/mn7xOULIptKqm6gG1KV4yTC+4ZVvGdAZvmHgh7MpSOYjviYTewWYA2a1F9ek5hrFikOVxI9fE2p608", - "lxQY784Pujh33nOilp8ebqsBCqjsOlXMpSOo4VvtbgL0wk4qra5Azpk4gZO+zadw+qIP6iuBL0P6i1Zq", - "ijbUnAMitEAVEdbjhUwyrKTop5cW4C9/c+fqkB84BVd/zsafGf62it379us37NQzTHOP8vtp6CgjO6FK", - "+6TDTkCS42ZxLtY7+U6+gCVaH5R89k4W3PLTBTciN6e1Af0VL7nM4WSl2LOQx/iCW/5ODiSt0SpzUQYp", - "q+pFKXJ2GSskLXlS5aDhCO/eveXlSr17934QmzFUH/xUSf5CE2ROEFa1zXzdk0zDNdcp35dp6l7gyFTY", - "aN+sJGSrmgykoa6KHz/N83hVmX7++3D5VVW65UdkaHx2t9syZqxq8ricgELQ4P7+oPzFoPl1sKvUBgz7", - "dcOrt0La9yx7Vz98+AQz4tqE8F/9le9oclfBZOvKaH5+36iCCye1ErZW86ziq5SL7d27txZ4hbuP8vIG", - "bRxlyfCzTrZeCMzHodoFBHyMbwDBcXRSLS7ugr4KNe7SS8BHuIX4jhM3Wsf/TfcrSk2/8Xb10tsHu1Tb", - "debOdnJVxpF42Jmm9NXKCVkhGsOIFWqrvkrYAli+hvzSl2+CTWV3887nIeDHC5qBdQhDhb0oMw9Ly6CD", - "YgGsrgruRXEud/0aHwasDWHFr+ESdm9UW5nmmKIe3RoTZuygIqVG0qUj1vjY+jH6m++jykKCpi/VgEmP", - "gSyeNXQRvhk/yCTy3sEhThFFpwbCGCK4TiCCiH8EBTdYqBvvVqSfWp6QOUgrriCDUqzEIlWT9O9Df1iA", - "1VGlL8Pmo5CbAQ0TS+ZU+QVdrF6911yuwF3P7kpVhpdUYjIZtIH60Bq4tgvgdq+dX8a1JAJ0qFJeY8Yy", - "WvjmbgmwdfstLFrsJFw7rQINRfSOj14+GY8/I8ChuCE84fNWUzgZ1XU96hLl18Kt3GC3UWt9aF5MZwgX", - "Pd8A1m9U125fHBTKlx6kChfR/VIbvoIR3SX23k2sI9Hx+OEghySSpAyiln1RYyAJJEGmlzO35uQZBvfE", - "HWJUM3sBmWEmchB7nxFWFPYIW5QowDaRq7T3XHe8qFQidQy0NGsBLVtRMIDRxUh8HNfchOOIxSMDl50k", - "nf2OFVv21ek6j2IJowqRTRWucBv2OehA7/fVukKJrlCXK1b6J9TYcroXpi+ktkNJFE0LKGFFC6eXA6G0", - "1WPaDXJw/LhcIm/JUmGJkYE6EgD8HOA0lweMkW+ETR4hRcYR2Bj4gAOzH1R8NuXqGCClr37Dw9h4RUR/", - "QzqxjwL1nTCqKne5ihF/Yx44gC/h0EoWvYhqHIYJOWeOzV3x0rE5r4u3gwzKRaFC0SsO5UNv7o8pGntc", - "U3TlH7UmEhJusppYmg1Ap0XtPRAv1DajzN6kLrLYLhy9J3MXMM84dTCpMNc9wxZqi+FceLVQrPwBWMbh", - "CGBEtpetMEiv+N2YnEXA7Jt2v5ybokKDJOMNrQ25jAl6U6YekS3HyOWzqNbWjQDomaHawvXeLHHQfNAV", - "T4aXeXurzdsakiEtLHX8x45QcpdG8De0jzXVsV71JZakBakbldQtDBYJ9ymid2xi6D4bOukMlIDqWtYR", - "orLLlE/baZ2AN85F+CwyK2H5MS5396NQNw0rYSy07o0QwfJHGI45Vj1Vajm+OlvppVvfa6Waa4ocvPhh", - "Z5mffAUYK74U2tgMfUPJJbiXvjFo7vjGvZqWlbrBdFQjXBRp3oDTXsIuK0RZp+nVz/vdCzftDw1LNPUC", - "+a2QFEq0wJr2yRDbPVNTFPbeBb+kBb/kd7beaafBveom1o5cunP8i5yLHufdxw4SBJgijuGujaJ0D4OM", - "UqOH3DGSm6Loi5N9dvHBYSrC2AfjqUKC9tgdRSMl1xIVekvnsqnVCopQwCp4bmRUJqxUchU1X6mqfVXR", - "ThgVJ8PaYnvKkvmAcRgLF48E00zIArZp6GP5FSFvc8CwpBpOsgJJhTXSBowkauJgdHwjsip9Yq9dP1Q9", - "Ga77pud2beNoaZea7cQNKIEXXno2ENa3/1gON8Sjbj4W6Nupbbn/COGASFPCRv0IhgnzIwyYV5Uotj0X", - "CY06aq7hR9lBQxXXHlaQtfjBDmCgG66bJLhOBVwfFOxNwaeonZ06/YGihH0IrKNvnvtU8aLWaGvvxOAO", - "yy03WsXEtX/384VVmq/A+0syAulWQ+ByjkFDVMzYMCso8KEQyyXEfgJzExt3B7iBNbiYQLoJIks7E2oh", - "7RdPU2R0gHpaGA+jLE0xCVoY8x6/GfpjgkwfGT2aKyHamhs4VZKJ5d/BLvvZqces4kKbNpDUO0i6l+8R", - "u361+Q52OPLB+EwH2IFdQRvJa0AaTNmkm0eUT9Xo33FlbqwF09nCI3bqLL1Ld7Q1vpb6OPG3t0yn1nh3", - "Kbc5GK0738EyZTcu0l50d3qgi/g+KR/aBFEclkEieT+eSpjQeW54FTVVEw7R7hvgZSBeXM7s43x2O591", - "6jbzIx7A9avmAk3iGWMiyYfZCUE5EuW8qrS64mXmPftjl79WV/7yx9dDIMAn1mTSlP3m67OXrzz4H+ez", - "vASus8YSMLoqfK/6l1kVVV/ff5VQPWdvkiNLUbT5Tc3dOBrgGms394xNg14GbaRHdBR9dMAyHZp9kPf5", - "oBRa4p7gFKia2JTWO0ehKd1wFH7FRRncYgHakTBqXNy0hhhJrhAPcOuwlig6KbtTdjM43enT0VLXAZ6E", - "c/2IRRTTGof0JRaRFfkwFX7n0tM3SneYv8+hS4a5/H5ilROyCY8jUcWh7VxfmDphJHj9uvrVncYHD+Kj", - "9uDBnP1a+gcRgPj7wv+O+sWDB0k/V9KM5ZgEWqkk38D9Jh9gdCM+rQIu4XraBX12tWkkSzVOhg2FUrxK", - "QPe1x961Fh6fhf+lgBLcTydTlPR40wndMTBTTtDFWM5cEw65oU53hinZj/7FdE1HWsjsfdF9chsOj5Cs", - "N+hqy0wp8nQQglwYx14lhf25lxm+PGKtdSPWYiSKVNYiGsu9NqW6Zw/IaI4kMk2ywGiLu4Xyx7uW4p81", - "MFE4rWYpQOO91rvqgnKAow4E0rRdzA9MISzt8Lexg8R9bPoyo7cF7TOCxEGGA3BfND6lsNDGZdvqTMfG", - "KsczDhj3njhjTx+eminvat0NFpymx0zpeBwYnW+oMzJHsoOxMNlSq98g7QhB/1GiZEPo3CPQzPsbyFSM", - "WZ+lNO7PthFzO/uh7Z6uG49t/K114bDoplnQTS7T9Kk+biNvovSadGFhj+QxJSz2hXeD2EdYCx6vKGwT", - "G12EOBku6TxRvYJOLlT6VMZZh6c0fnsqPcyDTM2SXy94qguI04UcTNH2diJ6rGLh47ABpsnGp9lZFGvc", - "vCuo5lkFuvVBDOun3lCvoWknazStAoMUFasuc4pCLI1KDFPLay6p+a/7jviV/9oAueDdV9dKY8VCkw4+", - "KiAXm6Q59t27t0U+DDQpxEpQX9vaQNQ41Q9EPcOJinzz2abGhEfN+ZI9nEfdm/1uFOJKGLEoAd94RG8s", - "uMHrsnGHN5+45YG0a4OvP57w+rqWhYbCrg0h1ijW6J4o5DUhdAuw1wCSPcT3Hn3JPsPgQSOu4L7DoheC", - "Zs8efYmhH/THw9Qt6/sS72PZBfLsEFacpmOMnqQxHJP0o6bjhJca4DcYvx32nCb6dMpZwjf9hXL4LG24", - "5CtIZxJsDsBE3+Juoju/hxdJ3gAwVqsdEzY9P1ju+NNIdrJjfwQGy9VmI+zGh5gZtXH01HZFpUnDcNSi", - "23cECnCFhxipWYVAtZ6t6xOrMXwzkl2E8bQ/oI82RuuccSpTWYo2hjq02WPnoQoutkhqOiMRbtxcbuko", - "S2JI9ZJVWkiL9o/aLrO/OLVY89yxv5MxcLPFF08TrYa63TjkcYB/crxrMKCv0qjXI2QfZBb/LftMKplt", - "HEcp7rfVAKJTORpSmg4eHItg3D/0VMnXjZKNklvdITcecepbEZ7cM+AtSbFZz1H0ePTKPjll1jpNHrx2", - "O/TT65deytgonSpt3x53L3FosFrAFeZ2pTfJjXnLvdDlpF24DfR/bPxTEDkjsSyc5aQiEHk096V1Oyn+", - "5+/bGt3oWKWcuZ4NUOmEtdPb7T5xtOFxVre+/5YCxvDZCOYmow1HGWJlJE6cAsGbb/6IeKE+SLTnHYPj", - "o1+Zdjo4yvEPHiDQDx7MvRj86+PuY2LvDx6kS+UmTW7u1xYLt9GI8dvUHn6lEgaw0JeuCSjymfwJA+TY", - "JeUeOCa48EPNWbcH2KeXIu4mEykdbZo+Be/evcUnAQ/4Rx8RfzCzxA1s4+nHD3u3B2KSZIrmeRTnztlX", - "ajuVcHp3UCCePwGKRlAy0TyHKxn0eEy66w/Gi0Q06kZdQKmckhm3r4nt+f86eHaLn+/Bdi3K4ue2Clnv", - "ItFc5utklPDCffgLyeidK5hYZbIjxppLCWVyONJtfwk6cEJL/4eaOs9GyInv9nuM0nJ7i2sB74IZgAoT", - "OvQKW7oJYqx2Czw1BQTKlSoYztO2X2iZ47BZb9RB8J81GJs6GviAUuXQ2eWYLzWwYyALtH6dsG+x1IqD", - "pVNbG61OoWppt4JfXZWKF3Ospvrm67OXjGalb6jXMzXQW6HRpbuKpJX8iI7gobd9ulTHMZ3F99UOcKs2", - "Nmv63aWKobk32o58ohc6geaYGDsn7AVZwppO2zQJw5q8egNF1F6PdDGkCfcfa3m+RhNT5yIbJ/npnR8D", - "VbYGeB7+n7ftVvDcObh980fq/Thnyq5BXwsDmAIMV9Ctv9YUI/QmzlCPrbs8XUtJlHJyhEzRNFc5Fu0B", - "OBJIgm84CVkP8UcaGKhx6rGNMC/wq3RIfa+rZs95G6p5Ne3Bv/c24pxLJUWOtddTAhHWiprmbZpQpj7t", - "JjIzf0IThyvZy7NJPvRYHO3uGRihR9zQcxs9dZtK1EF/Wtj6Hk8rsMZzNijmoSWt92sIacC3z3FEFPNJ", - "pROxKcl49sYPfiQZYRmYEUPVN+7ZD96MiVn4l0KiwcKjzYvZ5HkojUAHo2TCspUC49fTTcowb903J1gW", - "roDt+5OXaiXyC7HCMSgayi2bQv+GQ52FQEAfeOfefe7e9cW6m587UT006VlV+UnHGxanu7Rv5SiCU+En", - "IR4gQm4zfjzaHnLbG8GL96kjNLjC4COo8B4eEEbTvLfXKd+pCERR+AajxLhkxU4hE2C8FDJ4wtIXRJ68", - "EnBj8LyOfGdyzS2JgJN42hvg5UgcOyaakiv1tkP1S5U7lOAawxzj29j2HR5hHM0LreDG5Y6FQ+GoOxIm", - "nvOyiYBNdBFGqcoLUQXmiPT6CqcYh2PcoXN59wI4mIXVfI7l/4+9icaKoi3qYgU240WRqqXzFT5l+DTk", - "+sAW8rrpetMkeXWLIg+pzU+UK2nqzZ65wgu3nC5q1J2ghrhZeNhhLO2x2OG/qZYv4zvjY1+PTq4Mga7F", - "cZXAh8miKanX0XRmxCqbjgm8U26PjnbqmxF6+/2dUnrIuvxTJFX2uFy8Ryn+9rW7OOJKoYMwY7pamkKe", - "GNKr8HmosNKUoOtyJbzKBo2N0HmNm5fYsh7w4cUk4Fe8HElojk3edL+SGXgsrTkfzcLn1tcDspztZUGj", - "NVYo5LNnRB96gsbCPCnK8+6Mz36texE67oL5ruNwoVCfllmMOlpu5gtpN/hYZ8h3V2OZ7qExAD7vN2q/", - "BF++sdJwJVQdgmhCKGtQCenXTtvzptZAcv3JAPE/2vg8aip/4xtm0jK9Tv7dz+RMYyCt3v0JDOeDTR+0", - "gB9Ku2Seal9hTa+1Sb3XOrfilKYZqf4MXjbsNKE/0EJ/QFYvpogDw5b489l5cdSFmerxMaNRUscu3eB+", - "vAR6W/Ycj1iljGhbHqY630+MGX+DzeujEu7DsUIs4RXkFvtctjFSGuCYgu5usmC7/3+l0MfV6Sa03ldA", - "31f2fNjc8sAdP6h/E9VwosaAJ9OLfJ81kbCUyHPNTVt1o5f6OjkBb7mEHMuw7q039Pc1yKiWzTzYZRCW", - "ZVR+SDTpKFhI+HirYwvQvnJAe+GJGnrcGpyxdORL2N0zrEMNyU6FTS7WTSqVIgaQO2ShaO2YIdkH/wjT", - "UAZiIUR2+tqvbTX+0SKzUfWsG84VSNJdHG1FrT1TprssT5rLfXpUnTnMrBgrSTRs0jquf7zAnrjGxznx", - "ptJprKWz82GnjmtfKRWrQzW+k1AzFUz4LZSCo1lKcQlxG3b0VF1zXYQ37qS2D91NIg30splZtHH4Q191", - "ovY7prTkpXJiRDaWF9QNfW/ixu4ZCvBr67AgXEvQGorGJVIqA5lVIW5/Hxz7UEFRjDdCghntt0LAjdba", - "fd0WE8a+Uxxr63IfvBgvkGnYcAedjkr+js+5D9nP6XnIpQ59hw5amBp6PdwAM2RgCDNAYkz1S+Zvy8M5", - "2jcxNgkpQWfB89Sv/yu7hbWwfGJR53RBxwejMchNLoGyh5Uk7TT5cJU9HSHKdb6E3SkpQaFzaNjBGGiS", - "nAj0qG5kb5Pv1PxmUnCv7gS8P7YcWKVUmY04O86HRYv7FH8p8kvAUm5NpPJIU2j2GdrYG2/29XoXivRW", - "FUgo7p8wdiYpNyQ4trv9zHqTy3t23/xbnLWoqY64N6qdvJPpIHus8K1vyc3CMPt5mAHH6m45FQ1yoCTu", - "dqRgsubXiRbpJ1O18qGrud+2uiUqgiIlk1yQx+o5HvSU4Qgz2aOSC+jI5Mx7upgpVSok8ybZ9m6oNKbi", - "yRAgC3JK0ncDhR88iYBkI+bEKaQKZr52mVoyDa0T+aZF3IY9o1MafX/mZpYuv1sqDZ3uz+5rKtjY5C+E", - "Nu1cL4TVXO9uUmpt0LN6YD0ZxfLBcKwmEqtdSBuNNcRhWarrDJlV1hTWT6m27j3TvYxDl6f2O3eqFxDF", - "dXHjBbUdW/OC5UpryOMv0ml7BNVGachKhWFeKQ/00jq5e4O5OpKVasVUlasCqEFFmoLG5qql5Cg2QRRV", - "k0QB0Q4mfdI3ER1PnPKuGqZTcR5adEa+zJHAUzC+GI/HEL08hHdPs/GjWkOcL9EiJDDWpZt7TdJn3HId", - "juy4LsoyGAzGmq6zn0yN4UiYeOOmeMo2yliv2dFIphmqDfH6LFfSalWWXSMQicQrb9n+nm/P8ty+VOpy", - "wfPL+6hHSmWblRbzkJbaD8ZrZ9K9ikwTu8P3K5zSexia5onk6BbwnnMc3bk5AvP9YY512MZ9lupw311X", - "l3ml1YYzybhVG5GnafhfK7ptNCYtxRKSpZ6oeRol5+NryKjjy6EJZkCWNEQzSJ7s/nTGPE/zTl1kHu6/", - "KPH2x2VL8JfEyMU05JNeasnyUdmqBwBCShmjttbUcS2WfBquolaUYY4u6T6gE7k4Rv7cDjY3wp0DZeFW", - "QA2iDRsAPyNlf04luShycaG24fn9tmbXjYD/uJ/KO8xjLKTqoiUtTUFVob7HCEdIVwbeG3/0BrOFF1Oj", - "kJrumBNv1AiA8bikDgyTopOOBWPJRQlFlmqudt7YhOaRZuszWvo9j4XxnDzndeht5sauNfh6EyRS666/", - "qeKOlFTz+tByKwvYgsFiENTonRvyMwR/B5TU06ynfKsqK+EKOuFavghGjaKduILwrWk+ZgVAhd6/vk0q", - "FYcU3+U9Q4VfexZFskzBbtJyQYilnWIHzBJJI8pWZnRMzNSj5CC6EkXNO/gzx4ocXbObO8oJVA1k8izo", - "bVOn+YlGeB0GOAvfp0SZgIn30/jQ0Swojbp9DOhgXGJtxk69TIclxhVeGocGzlY0jk8i8ZZvmIpfy3ED", - "4JDkW/Vm4j4JJSPEfr2FHKWabtzd7XHCcDBmetWbRkVw3ezwzQ3JfwgN7yXh0fFSqoYBZLB7LTWBLrzA", - "ji9gl1vpxF4nNWP/Ms//Pf+bs0UdBnJ6NbVTizW4FxA8dlhQunFWeIFWNBdaiC+c+3qCfaVcRJHVG75j", - "SuM/Tl/7Z81LsdzhCSXww2fMrLkjIe8iJN+1j1d0E+8XTOYBsGAXUGEqWreYOmY03M6NEgHtrsDQTUSx", - "Db+EeBvQLU+cJ7eO5Zh6sRHG4GXX284hFvziQ02IDS9iHRkr03U7DIdape7r/7/N2oqnCgWlqpLnoXme", - "74nSMYhTg8xAXHYNm/1pfUP1OJBA03SzJVod0nmLGxj3jozcSMXKj/V76IA9aEY4aHVxq2Uc07e8zYze", - "kxA5aSl3vQtT40MGQKOTOVT1OgA+VWMMFcA+Bf6TRSPHljEF/D8L3kd6OMbwUrvGT4DlTsp/Alayqy7U", - "NtOwNIdCIciw6hRh3RYLCMZJIXMN3FBsyPmPXmVrayIK6VRIil5svG/NKAUshWyZpZBVbRMaAJZGlLsI", - "YbF5GtE64uwZkxKcGHbFyx+vQGtRjG2cOx3UQy6uSR9M8v7bhPLf3KnDAYRptR/MJIQ2Uy16zV3g1PWG", - "AguN5bLguohfF5LloN29z675ztzc9+Gg1bWTLw54P3gkzXTz2yM/CJI2AVLuvPvylp6JBkB+hy6KCa4F", - "jGBNuBXIKGLViCdhCEO6rALfZqVaYX7ZCAH64pPo+yFlRUk02JI8dNw8RvwG+6fButv+4FuFs06ZYv85", - "+xFRhwrPT1LYvSeNrGn9hD+KyKSDEOhfrtqwcNqcIf2ncjTfYBJDJ08zCHchiSHsNYWH0Hww4snoWnBH", - "dhEd5D7BNzbXTu9n1PXBpzJBSYfNULc1ewK/wbRBzjz3gTtDo89AKSakzH0e7ZE2IbIkh3tgBDxqk+zP", - "VnfaJpjCjXNME6j9mbNZpaosnxINSKX5C2/Q9pB2YRyhj8hcPbLuJnDCNM0qOoVNOl0rju2DNdo145Bf", - "psr3KdljBo0RDto1lqsl8jI8wmTGwRyPxngx72cfdQ02DZNgnGnIa40GzWu+O9xXaKQk7MXfzj5/9PiX", - "x59/wdwLrBArMG1Z4V5fnjZiTMi+neXTxogNlmfTmxDy0glxwVMW0m2aTfFnjbitaWsGDroSHWMJTVwA", - "ieOY6Adzo73Ccdqg7z/XdqUWeec7lkLB779nWpVluqx7I7olTP2p3YqM/U7ir0AbYaxjhF1fnbBtrKxZ", - "ozkOi3teUZ0RJXNffb2hAmFHgnFSCxkLtUR+hlm/3r/BYFuVnleRT2LfurxeRBYxDM7A+I0FsEpVXpQW", - "S5aCCHNLdJRz6Q2NGN4ZRU82zJbiKFOE6GOS06QXd8Tdz+273RptmtO7TUyIF+FQ3oA0xyzp4xntN+Ek", - "rSn9T8M/Ein6d8Y1muX+HrwiqR/crOv2JNCG6doJ8kAARvIwOxl0cVP+ttKoJqs82u+Dq7MvfnzfukAP", - "JgwgJOGDA+DFiZXte02MuwfnDy7Z+X2DlGgp78coobP8Q7magfU2F0m0Rd5IYS0YYktqKBZGibjmeZPf", - "OqKVDNJgsQO/00zLMpE+S3YTPFMx4TiVQF/x8tNzjW+ENvYM8QHF6/GkmTiHMkYyodLcrILbSz5p7ihf", - "8u6mlq8wZffv4PYoec/5oby7eHCbodULW1Kvwq1AWcDsGsekcKBHX7CFr6ZfaciF6buhr4Nw0qQMghZL", - "H3oJW3sgR/HQOn9W9hZkvAwxI+yHyJ2k0GzXQtge0T+YqYyc3CSVp6hvQBYJ/KV4VNx988B1ccvK6zcr", - "CBKV9jqyIMiwr+jU5VHRC3fp1AaG65x8W3dwm7io27VNrWYzuYD7u3dv7WJKEZp0sXX3OVbBuZOq60fV", - "XP8d6t8QjvwYft4Uxfw8VhGVqn6OFN/t7UctyoMBIp1Syh/nsxVIMMJgseBffHOIT3uXBggoJ394VAnW", - "2xQSIcQk1tqZPJoqKpI8oT6y/yxRDRnz3fJaC7vDxqDBgCZ+SVbq+bap+uCrhjS+K3/3WXUJTXPmtkZE", - "bcLt+q3iJd5H5FKT7hZS5Qn7ess3VenNweyv9xb/AU/+8rR4+OTRfyz+8vDzhzk8/fzLhw/5l0/5oy+f", - "PILHf/n86UN4tPziy8Xj4vHTx4unj59+8fmX+ZOnjxZPv/jyP+45PuRAJkBD7e5ns/+VnZUrlZ29Os/e", - "OGBbnPBKfAdub1BXXipsXOeQmuNJhA0X5exZ+Ol/hBN2kqtNO3z4deYbsMzW1lbm2enp9fX1SfzJ6QqT", - "wjOr6nx9GubBdmIdeeXVeRNNTnEvuKOt9Rg31ZPCGT57/fXFG3b26vykJZjZs9nDk4cnj3zvWskrMXs2", - "e4I/4elZ476femKbPfvwcT47XQMvsYaK+2MDVos8PNLAi53/v7nmqxXoE0wYoJ+uHp8GseL0g0+O/7jv", - "2WkcUnH6oVNDoDjwJYYDnH4IHSz3v93pXugjsaIPJkKx77XTBXatmPoqmOjl8aWgsmFOP6C4PPr7qbd5", - "pB+i2kLn4TQU2hh5k1Kq0w87KPxgt24h+4dz70Tj5dzm67o6/YD/QdKOVkQVGk/tVp6iW/X0QwcR/vEA", - "Ed3f28/jN642qoAAnFouqe3nvsenH+jfaCLYVqCFkxmpKop3ITcn8ryYPZt9Hb30fA355QxbhWFAHx61", - "xw8fJsrXRl8xOvl8UULhju3Th08nfCCVjT/y2VrDD3+Sl1JdS4bFDukaqDcbrncoXtlaS8N+/I6JJYP+", - "FMKEGZD1cEcpb2dVvShFPpvPOuh5/9EjjYp7nWJzrF2Ly/DzTubJH4fb3ClsNPLzabh4Uryn++aHzp/d", - "I2fWtS3UdTQLqmxkbxhC5h7Wpv/36TUX1glhvp4OdtMcfmyBl6e+eHbv17Ze5eAJFuGMfowj15O/nnKP", - "6lmlTIJsX/PryM56hi+TpALGfqWQ5c98v51erZfTbbYQEinow8w0TcZbSY0eDlW9wZXnFFcMSQjGrmEu", - "PCbkasWLnBvs4ujr0M9iscrqGj4mjx0ep4d71uKvsmgdew2PnYqhiRV9xQsW8pgz9j0vHVagYGdeHugs", - "jQ77o08H3bmkqFp3uEkk+jifff4p8XMunfTOy8CO3PRPPt30F6CvRA7sDWwqpbkW5Y79JJvA4Bsz0m+Q", - "ODXPL1FyawiWolg0v+7GGut0nmi3zUJIGwZmt2zNZVH6zDpVY4dWR1lonFaRe9RdQKHNSKU0AkD1m6Ag", - "h5E5YReNOw2dUxTVjt2SrqBUFVqPsCohTcLR1Ubm1vgi6PJ/p4q6Q7wCmXk2ki1UsQtNzzW/tltKkhvw", - "qqZ7ffJhX3RLPfXSychLIYwtPG7VuFgtmj17GylEb99/fO+e6SuMt3n7IZLyn52eYlzzWhl7Ovs4/9DT", - "AOKH7xuEhW5Ts0qLKyynjEhTWqyE5GXmpeu2I8ns8cnD2cf/GwAA//9Uoiarv/oAAA==", + "H4sIAAAAAAAC/+x9f5PbtpLgV0Fpt8qxT5yxHSf74qtXexM7yZuLnbg8TvZ2bV8CkS0JbyiADwBnpPj8", + "3a/QDZAgCUrUzMTJq9q/7BHxo9FoNBr988MsV5tKSZDWzJ5+mFVc8w1Y0PgXz3NVS5uJwv1VgMm1qKxQ", + "cvY0fGPGaiFXs/lMuF8rbtez+UzyDbRtXP/5TMM/aqGhmD21uob5zORr2HA3sN1VrnUz0jZbqcwPcUZD", + "nD+ffdzzgReFBmOGUP4oyx0TMi/rApjVXBqeu0+GXQu7ZnYtDPOdmZBMSWBqyey605gtBZSFOQmL/EcN", + "ehet0k8+vqSPLYiZViUM4XymNgshIUAFDVDNhjCrWAFLbLTmlrkZHKyhoVXMANf5mi2VPgAqARHDC7Le", + "zJ6+nRmQBWjcrRzEFf53qQF+g8xyvQI7ez9PLW5pQWdWbBJLO/fY12Dq0hqGbXGNK3EFkrleJ+xlbSxb", + "AOOSvf72Gfv888+/cgvZcGuh8EQ2uqp29nhN1H32dFZwC+HzkNZ4uVKayyJr2r/+9hnOf+EXOLUVNwbS", + "h+XMfWHnz8cWEDomSEhICyvchw71ux6JQ9H+vICl0jBxT6jxnW5KPP8fuis5t/m6UkLaxL4w/Mroc5KH", + "Rd338bAGgE77ymFKu0HfPsy+ev/h0fzRw4//8vYs+y//5xeff5y4/GfNuAcwkGyY11qDzHfZSgPH07Lm", + "coiP154ezFrVZcHW/Ao3n2+Q1fu+zPUl1nnFy9rRici1OitXyjDuyaiAJa9Ly8LErJalY1NuNE/tTBhW", + "aXUlCijmjvter0W+Zjk3NAS2Y9eiLB0N1gaKMVpLr27PYfoYo8TBdSN84IL+vMho13UAE7BFbpDlpTKQ", + "WXXgego3DpcFiy+U9q4yx11W7M0aGE7uPtBli7iTjqbLcscs7mvBuGGchatpzsSS7VTNrnFzSnGJ/f1q", + "HNY2zCENN6dzj7rDO4a+ATISyFsoVQKXiLxw7oYok0uxqjUYdr0Gu/Z3ngZTKWmAqcXfIbdu2//3xY8/", + "MKXZSzCGr+AVzy8ZyFwVUJyw8yWTykak4WkJceh6jq3Dw5W65P9ulKOJjVlVPL9M3+il2IjEql7yrdjU", + "GybrzQK029JwhVjFNNhayzGAaMQDpLjh2+Gkb3Qtc9z/dtqOLOeoTZiq5DtE2IZv//pw7sExjJclq0AW", + "Qq6Y3cpROc7NfRi8TKtaFhPEHOv2NLpYTQW5WAooWDPKHkj8NIfgEfI4eFrhKwInDDIKTjPLAXAkbBM0", + "4063+8IqvoKIZE7YT5654VerLkE2hM4WO/xUabgSqjZNpxEYcer9ErhUFrJKw1IkaOzCo8MxGGrjOfDG", + "y0C5kpYLCYVjzgi0skDMahSmaML9753hLb7gBr58MnbHt18n7v5S9Xd9745P2m1slNGRTFyd7qs/sGnJ", + "qtN/wvswntuIVUY/DzZSrN6422YpSryJ/u72L6ChNsgEOogId5MRK8ltreHpO/nA/cUydmG5LLgu3C8b", + "+ullXVpxIVbup5J+eqFWIr8QqxFkNrAmH1zYbUP/uPHS7Nhuk++KF0pd1lW8oLzzcF3s2PnzsU2mMY8l", + "zLPmtRs/PN5sw2Pk2B5222zkCJCjuKu4a3gJOw0OWp4v8Z/tEumJL/Vv7p+qKl1vWy1TqHV07K9kVB94", + "tcJZVZUi5w6Jr/1n99UxAaCHBG9bnOKF+vRDBGKlVQXaChqUV1VWqpyXmbHc4kj/qmE5ezr7l9NW/3JK", + "3c1pNPkL1+sCOzmRlcSgjFfVEWO8cqKP2cMsHIPGT8gmiO2h0CQkbaIjJeFYcAlXXNqT9snS4QfNAX7r", + "Z2rxTdIO4bv3BBtFOKOGCzAkAVPDe4ZFqGeIVoZoRYF0VapF88NnZ1XVYhC/n1UV4QOlRxAomMFWGGvu", + "4/J5e5Liec6fn7Dv4rFRFFey3LnLgUQNdzcs/a3lb7FGt+TX0I54zzDcTqVP3NYENDgx/y4oDp8Va1U6", + "qecgrbjGf/NtYzJzv0/q/M9BYjFux4kLH1oec/TGwV+ix81nPcoZEo5X95yws37fm5GNG2UPwZjzFot3", + "TTz4i7CwMQcpIYIooia/PVxrvpt5ITFDYW9IJj8ZIAqp+EpIhHbunk+Sbfgl7YdCvDtCANO8i4iWSIJs", + "VKhe5vSoPxnoWf4JqDW1sUESdZJqKYzFdzU2ZmsoUXDmMhB0TCo3oowJG75nEQ3M15pXRMv+C4ldQuJ7", + "nhoRrLe8eCfeiUmYI3YfbTRCdWO2fJB1JiFBrtGD4etS5Zd/42Z9Byd8EcYa0j5Ow9bAC9Bszc06cXB6", + "tN2ONoW+XUOkWbaIpjpplvhCrcwdLLFUx7CuqnrGy9JNPWRZvdXiwJMOclky15jBRqDC3D8cScNO7y/2", + "Dc/XTixgOS/LeasqUlVWwhWU7tEupAQ9Z3bNbXv4ceTwrsFzZMAxOwssWo1XM6GKTTe6CA1sw/EG2rjX", + "TFV2+zQc1PAN9KQgvBFVjVqE6KFx/jysDq5AIk9qhkbwmzWitiYe/MTN7T/hzFLR4kgDaIP5rsFfwy86", + "QLvW7X0q2ymULkhnbd1vQrNcaRqCbng/ufsPcN12Jur8rNKQ+SE0vwJteOlW11vU/YZ87+p0HjiZBbc8", + "OpmeCtMPMOIc2A/FO9AJLc2P+B9eMvfZSTGOklrqESiMqMicWtDF7FBFM7kGqG9VbEOqTFbx/PIoKJ+1", + "k6fZzKST9w1pT/0W+kU0O/RmKwpzV9uEg43tVfeEkO4qsKOBLLKX6URzTUHAG1UxYh89EIhT4GiEELW9", + "82vta7VNwfS12g6uNLWFO9kJN85kZv+12j73kCl9GPM49hSkuwVKvgGDt5uMGaebpbXLnS2Uvpk00btg", + "JGutjYy7USNhat5DEjatq8yfzYTFghr0BmodPPYLAf3hUxjrYOHC8t8BC8aNehdY6A5011hQm0qUcAek", + "v04KcQtu4PPH7OJvZ188evzL4y++dCRZabXSfMMWOwuGfebVcszYXQn3k68jlC7So3/5JNiouuOmxjGq", + "1jlseDUcimxf9PqlZsy1G2Kti2ZcdQPgJI4I7mojtDMy6zrQngvj3k6bxZ1sxhjCinaWgnlICjhITMcu", + "r51mFy9R73R9F2oB0Frp5NVVaWVVrsrMyUdCJR72r3wL5lsEzUbV/52gZdfcMDc3Wv1qWYy83+1WTuf7", + "NPSbrWxxs5fz03oTq/PzTtmXLvJb6b0CndmtZAUs6lVHrbDUasM4K7Aj3tHfgSW5RWzgwvJN9eNyeTda", + "QoUDJfQfYgPGzcSohZMaDORKkhPcAVWHH3UKevqICdYZOw6Ax8jFTuZoYrqLYzuuBdoIifZus5N5pBJy", + "MJZQrDpkeXvVzxg6aKp7JgGOQ8cL/Iw67udQWv6t0m9ase87rerqzoW8/pxTl8P9YrwWvXB9g/pUyFXZ", + "dbxcOdhPUmv8Qxb0rHl80xoQeqTIF2K1ttE765VWann3MKZmSQGKH0jJUro+Q1XLD6pwzMTW5g5EsHaw", + "lsM5uo35Gl+o2jLOpCoAN782aeFsxFUPfYTQtcnG8h6+64VhC3DUlfParbauGDruDO6LtmPGczqhGaLG", + "jLgtNP4m1IqmIzewUgMvdmwBIJlaeN8A77WAi+TodWSDeONFwwS/6MBVaZWDMVBkXoV7ELTQjq4OuwdP", + "CDgC3MzCjGJLrm8N7OXVQTgvYZehj5xhn33/s7n/B8BrleXlAcRimxR6+3qoIdTTpt9HcP3JY7IjDRdR", + "LbMKpdkSLIyh8CicjO5fH6LBLt4eLVeg0RXjd6X4MMntCKgB9Xem99tCW1cjnt/+eeskPLdhkksVBKvU", + "YCU3NjvEll2jzhvcrSDihClOjAOPCF4vuLHkPiRkgbpAuk5wHhLC3BTjAI8+Q9zIP4cXyHDs3N2D0tSm", + "eY6YuqqUtlCk1oCWzNG5foBtM5daRmM3bx6rWG3g0MhjWIrG98iilRCCuG3slt4SOlwc2qLdPb9LorID", + "RIuIfYBchFYRdmPv1xFAhGkRTYQjTI9yGpfb+cxYVVWOW9islk2/MTRdUOsz+1PbdkhcZByge7tQYNDw", + "4Nt7yK8Js+T3vOaGeTiCaRrVIOTnNITZHcbMCJlDto/y8YnnWsVH4OAhrauV5gVkBZR8lzCq02dGn/cN", + "gDvePneVhYwcWNOb3lJy8BfcM7TC8UxKeGT4heXuCLqnQEsgvveBkQvAsVPMydPRvWYonCu5RWE8XDZt", + "dWJEvA2vlHU77ukBQfYcfQrAI3hohr45KrBz1r49+1P8Jxg/QSNHHD/JDszYEtrxj1rAiA7VxwZF56XH", + "3nscOMk2R9nYAT4ydmRHFLqvuLYiFxW+db6H3Z0//foTJA3OrADLRQkFiz7QM7CK+zNyveyPebOn4CTd", + "2xD8gfItsZzg3tIF/hJ2+OZ+RT79karjLt6yiVHd/cQlQ0CDp7ATweMmsOW5LXdOULNr2LFr0MBMvSDT", + "/9AOYVWVxQMk7Rp7ZvRWzaRNca+Z9QKHipaX8tGiN8F++N70HgYddPi3QKVUOUFDNkBGEoJJPhesUm7X", + "hQ8bCoEjgZI6QHqmjSbt5vq/ZzpoxhWw/1Q1y7nEJ1dtoZFplEZBAQVIN4MTwZo5vVNfiyEoYQP0ksQv", + "Dx70F/7ggd9zYdgSrkOsnWvYR8eDB6jHeaWM7RyuO9CHuuN2nrg+0ODjLj7/CunzlMOeQn7kKTv5qjd4", + "YyVyZ8oYT7hu+bdmAL2TuZ2y9phGpnlJ4biTbDldv5rBunHfL8SmLrm9C6sVXPEyU1egtSjgICf3Ewsl", + "v7ni5Y9NN4wjhNzRaA5ZjtFvE8eCN64PBcy5cYQU7gCTs/xUgOCcel1QpwNPzNbDU2w2UAhuodyxSkMO", + "FCfmJEfTLPWEkQd5vuZyhQ8GreqVdwqlcZDh14ZUM7qWgyGSQpXdygyV3KkLwLt3hVBBJ04Bd0+6voac", + "HjDXvJnPR4dOuZmjPehbDJJGsvls9MXrkHrVvngJOd14xwmXQUfei/DTTjzRlIKoc7LPEF/xtrjD5Db3", + "91HZt0OnoBxOHHnKth/HnGXdc7vc3YHQQwMxDZUGg1dUrKYy9FUt49jm4GK3MxY2Q00+df1l5Pi9Hn0v", + "KlkKCdlGSdgl03kICS/xY/I44TU50hkFlrG+/TdIB/4eWN15plDjbfGLu90/oX2LlflW6bsyidKAk8X7", + "CRbIg+Z2P+VN7aS8LBOmRR/52GcAZt44uQrNuDEqFyiznRdm7r1pyRrpwyS76H/VxHPcwdnrj9uzocVB", + "9agjhrJinOWlQA2yksbqOrfvJEcdVbTUhPNTeIyPay2fhSZpNWlCi+mHeic5Or41mqukw8YSEmqabwGC", + "8tLUqxUY23vrLAHeSd9KSFZLYXGujTsuGZ2XCjR6IJ1Qyw3fsaWjCavYb6AVW9S2K/1jYK+xoiy9Qc9N", + "w9TyneSWlcCNZS+FfLPF4YLRPxxZCfZa6csGC+nbfQUSjDBZ2knrO/qK/vB++WvvG49u4vQ5OGu2mQZm", + "bpmd5CL/97N/f/r2LPsvnv32MPvqf5y+//Dk4/0Hgx8ff/zrX/9f96fPP/71/r//a2qnAuypsFMP+flz", + "/zI+f47Pn8jFvQ/7J9P/b4TMkkQWe3P0aIt9hikWPAHd7yrH7BreSbuVjpCueCkKx1tuQg79G2ZwFul0", + "9KimsxE9ZVhY65GPiltwGZZgMj3WeGMpaujXmA7wRqOkj9nG87KsJW1lkL4pfjH4l6nlvAnip/xeTxlG", + "eK95cI70fz7+4svZvI3Mbr7P5jP/9X2CkkWxTcXfF7BNvRXj4IJ7hlV8Z8CmuQfCnnSlI9+OeNgNbBag", + "zVpUn55TGCsWaQ4XQn28zmkrzyU5xrvzgybOnbecqOWnh9tqgAIqu07l/ekIatiq3U2AnttJpdUVyDkT", + "J3DS1/kU7r3onfpK4MsQ/qKVmvIaas4BEVqgigjr8UImKVZS9NMLC/CXv7nz55AfOAVXf87Gnhn+tord", + "++6bN+zUM0xzj1JB0NBR8H7iKe2DDjsOSY6bxbFY7+Q7+RyWqH1Q8uk7WXDLTxfciNyc1gb017zkMoeT", + "lWJPQxzjc275OzmQtEYTEkbBxqyqF6XI2WX8IGnJk5JMDUd49+4tL1fq3bv3A9+M4fPBT5XkLzRB5gRh", + "VdvMp8jJNFxznbJ9mSZFCo5MObD2zUpCtqpJQRpS8Pjx0zyPV5Xpp0oYLr+qSrf8iAyNTwTgtowZq5o4", + "Lieg+FBYt78/KH8xaH4d9Cq1AcN+3fDqrZD2Pcve1Q8ffo4RcW3ugF/9le9oclfBZO3KaCqHvlIFF07P", + "SthazbOKr1Imtnfv3lrgFe4+yssb1HGUJcNunWi94JiPQ7ULaEKDRzeA4Dg6qBYXd0G9QjrE9BLwE25h", + "N3D5VvsVxZ3feLsOxK7z2q4zd7aTqzKOxMPONFnSVk7ICt4YRqzwteoTyi2A5WvIL32mL9hUdjfvdA8O", + "P17QDKxDGMoBR5F5mIUIDRQLYHVVcC+Kc7nrp4MxYG1wK34Nl7B7o9okRsfkf+mmIzFjBxUpNZIuHbHG", + "x9aP0d9871UWAjR9Vg8Megxk8bShi9Bn/CCTyHsHhzhFFJ10GWOI4DqBCCL+ERTcYKFuvFuRfmp5QuYg", + "rbiCDEqxEotU+tr/GNrDAqyOKn3GPu+F3AxomFgy95Rf0MXqn/eayxW469ldqcrwkrKRJp028D20Bq7t", + "Arjdq+eXcSKHAB0+Ka8xYhk1fHO3BNi6/RYWNXYSrt2rAhVF1MZ7L5+M+58R4FDcEJ7QvX0pnIy+dT3q", + "Epn6wq3cYLd51nrXvJjOEC76vgFM9amu3b44KJTPUknJUKL7pTZ8BSNvl9h6NzGPRMfih4MckkiSMoha", + "9kWNgSSQBJkaZ27NyTMM7os7xPjM7DlkhpnIQOxtRph82iNsUaIA23iu0t5z3bGiUjbdMdDSrAW0bEXB", + "AEYXI/FxXHMTjiPmGQ1cdpJ09jumS9mX0u088iWMkok2CdvCbdjnoIN3v0/sFrK5hRRu8aN/Qjo29/bC", + "8IXUdiiJomkBJaxo4dQ4EEqbaKjdIAfHj8sl8pYs5ZYYKagjAcDPAe7l8oAxso2wySOkyDgCGx0fcGD2", + "g4rPplwdA6T0iZJ4GBuviOhvSAf2kaO+E0ZV5S5XMWJvzAMH8CkcWsmi51GNwzAh58yxuSteOjbn3+Lt", + "IIPMYvig6OUR864398ceGntMU3TlH7UmEhJusppYmg1Ap0XtPRAv1DajyN7kW2SxXTh6T8YuYJxx6mBS", + "Drd7hi3UFt258GohX/kDsIzDEcCIdC9bYZBesd+YnEXA7Jt2v5ybokKDJOMVrQ25jAl6U6YekS3HyOWz", + "KC3bjQDoqaHaGgdeLXFQfdAVT4aXeXurzdt0oyEsLHX8x45QcpdG8DfUj3UTqf2tTZg3npQrnKhPkkFu", + "qFm6TWY/6lxRtr5jEvv1yaEDxB6svurLgUm0dn29uniNsJZiJY75Do2SQ7QZKAEfwVlHNM0uU54C7i0P", + "eI9fhG6Rsg53j8vd/ciBUMNKGAut0Sj4Bf0R6niOaYeVWo6vzlZ66db3Wqnm8iezOXbsLPOTrwA98JdC", + "G5uhxS25BNfoW4NKpG9d07QE2nVRpCT9okhzXJz2EnZZIco6Ta9+3u+fu2l/aC4aUy/wFhOSHLQWWFQi", + "6bi8Z2rybd+74Be04Bf8ztY77TS4pm5i7cilO8c/ybnoMbB97CBBgCniGO7aKEr3MMgo4HzIHSNpNPJp", + "OdlnbRgcpiKMfdBLLYS9j938NFJyLVH6vHSEoFqtoAhpwYI9TEbJ10olV1H1o6ral2vuhFHKN8zYtifZ", + "m3fDhzEn/Ejcz4QsYJuGPn4VIORtZB0mqsNJViApXUlaLZRETezijy0iXd0ntoX2AwCSTtBvesbs1juZ", + "dqnZTtyAEnjh3yQGwvr2H8vhhnjUzcfcpzsZQ/cfIRwQaUrYqCDIMA3BCAPmVSWKbc/wRKOOKsH4Udrl", + "EWkLWYsf7AAGuk7QSYLrpKD2rtZewX6Kb95T9yoj32vvWOzom+c+AL+oNVowOp7Nw3znzVtt4tq///nC", + "Ks1X4K1QGYF0qyFwOcegIcombpgV5E5SiOUSYuuLuYnloAPcQMdeTCDdBJGlTTS1kPbLJykyOkA9LYyH", + "UZammAQtjNnk3wytXEGmj1RJzZUQbc0NTFXJcP3vYZf9zMvaPTKENq17rjc7dS/fI3b9avM97HDkg16v", + "DrADu4Kap9eANJjS9DefTJT4+Z7ppMbH52VnC4/YqbP0Lt3R1vhiBuPE394ynWT/3aXc5mC0ThIOlim7", + "cZH2TXCnB7qI75PyoU0QxWEZJJL346mECaUfh1dRk4viEO2+AV4G4sXlzD7OZ7fzBEjdZn7EA7h+1Vyg", + "STyjpylZhjuOPUeinFeVVle8zLy/xNjlr9WVv/yxeXCv+MQvmTRlv/nm7MUrD/7H+Swvgeus0QSMrgrb", + "Vf80q6LyB/uvEsqS7RWdpCmKNr/JZBz7WFxjRuyesmlQTKT1n4mOove5WKYd3g/yPu/qQ0vc4/IDVePx", + "09o8yeGn6+TDr7gog7ExQDvinI6Lm1aRJskV4gFu7SwU+Xxld8puBqc7fTpa6jrAk3CuHzE1ZfrFIX3i", + "SmRF3vmH37n09K3SHebvIxOTzkO/n1jlhGzC44ivdqj72BemThgJXr+ufnWn8cGD+Kg9eDBnv5b+QwQg", + "/r7wv+P74sGDpPUwqcZyTAK1VJJv4H4TZTG6EZ/2AS7hetoFfXa1aSRLNU6GDYWSF1BA97XH3rUWHp+F", + "/6WAEtxPJ1Me6fGmE7pjYKacoIuxSMTGyXRDpSYNU7LvU41BsI60kNn7UgZkjB0eIVlv0ICZmVLkadcO", + "uTCOvUpypnSNGTYe0da6EWsx4psraxGN5ZpNyZnaAzKaI4lMk0zb2uJuofzxrqX4Rw1MFO5VsxSg8V7r", + "XXXhcYCjDgTStF7MD0x2qnb42+hB9tibgi5onxJkr/3ueWNTCgtNFcs50gM8nnHAuPd4b3v68NRM0Wzr", + "rgvmtHfMlJLjgdF5Y93IHMkS4sJkS61+g7QhBO1HiUQYwfApUM37G8iU516fpTRG5bYSejv7oe2e/jYe", + "2/hbv4XDoptqXTe5TNOn+riNvMmj16TTNXskjz3CYg+DbmjACGvB4xU5w2L5kOB9xCWdJ8oC0YkwS5/K", + "OJbzlMZvT6WHeRD/WvLrBU/VVnFvIQdTtL0dPymrWOgcNsA0OQ5odhZ5cDdtBWWSq0C3NohhVtobvmto", + "2skvmvYBgxQVP13m5KZQGpUYppbXXFL1bdeP+JXvbYBM8K7XtdKYB9KkXboKyMUmqY599+5tkQ/ddwqx", + "ElRYujYQVS72A1HRfqIiX/25ydzhUXO+ZA/nUfl0vxuFuBJGLErAFo+oxYIbvC4bc3jTxS0PpF0bbP54", + "QvN1LQsNhV0bQqxRrHl7opDXOCYuwF4DSPYQ2z36in2GLplGXMF9h0UvBM2ePvoKHWroj4epW9YXBt/H", + "sgvk2cFZO03H6JNKYzgm6UdNe18vNcBvMH477DlN1HXKWcKW/kI5fJY2XPIVpOMzNgdgor64m2jO7+FF", + "kjUAjNVqx4RNzw+WO/40EvPt2B+BwXK12Qi78Y57Rm0cPbVliWnSMBzVyPd1lgJc4SP6v1bB/a+n6/rE", + "zxi+GYnZQi/lH9BGG6N1zjgl/yxF65ke6lyy85BbGAtPNfWmCDduLrd0lCXRUX3JKi2kRf1HbZfZX9yz", + "WPPcsb+TMXCzxZdPEgWcujVO5HGAf3K8azCgr9Ko1yNkH2QW35d9JpXMNo6jFPfbHAvRqRx11E27ZI75", + "he4feqrk60bJRsmt7pAbjzj1rQhP7hnwlqTYrOcoejx6ZZ+cMmudJg9eux366fULL2VslE4VDGiPu5c4", + "NFgt4Aoj5tKb5Ma85V7octIu3Ab6P9b/KYickVgWznLyIRBZNPcFyzsp/ueXbeZzNKxSJGJPB6h0Qtvp", + "9Xaf2NvwOK1b335LDmP4bQRzk9GGowyxMuJ9T+71TZ8/wl+oDxLteUfh+OhXpt0bHOX4Bw8Q6AcP5l4M", + "/vVx9zOx9wcP0gmIkyo392uLhdu8iLFvag+/VgkFWKj21zgU+fwICQXk2CXlPjgmuPBDzVm3stqnlyLu", + "Jr4r7W2aPgXv3r3FLwEP+EcfEX8ws8QNbKMUxg97t7JkkmSK5nvk587Z12o7lXB6d1Agnj8BikZQMlE9", + "hysZVM5MmusP+otENOpGXUCp3CMzLgoU6/P/efDsFj/fg+1alMXPbW633kWiuczXSS/hhev4C8nonSuY", + "WGWyzsiaSwllcjh62/4S3sCJV/rf1dR5NkJObNuv3ErL7S2uBbwLZgAqTOjQK2zpJoix2k2b1aRlKFeq", + "YDhPW9SiZY7DEshRXcZ/1GBs6mjgBwpARGOXY75UFpCBLFD7dcK+wwQ2DpZOxnLUOoVcsN28iHVVKl7M", + "MUftm2/OXjCalfpQBW0qS7hCpUt3FUkt+RF11r3SeSQByjH12vdlZHCrNjZrqgimUsy5Fm2dQ9FznUB1", + "TIydE/acNGFN/XKahGGmY72BIipaSG8xpAn3H2t5vkYVU+ciGyf56fU0A1W2CvgoiKwpYoPnzsHtS2pS", + "Rc05U3YN+loYwMBquIJuVrsmxaNXcYYsd93l6VpKopSTI2SKpmTNsWgPwJFAEmzDSch6iD9SwUDlaI8t", + "L3qBvdIu9b1apT3jbciR1hRdf+l1xDmXSoocM9qnBCLMwDXN2jQh+X/aTGRm/oQmDleyQmoT0umxOFoz", + "NTBCj7ih5Tb66jaVqIP+tLD1lbNWYI3nbFDMQ6Ffb9cQ0oAvSuSIKOaTSid8U5L+7I0d/EgywuQ6I4qq", + "b923H7waE3MbXAqJCguPNi9mk+WhNAINjJIJy1YKjF9PNyjDvHV9TjDZXgHb9ycv1ErkF2KFY5A3lFs2", + "uf4NhzoLjoDe8c61feba+hTozc8drx6a9Kyq/KTjZaDTte+3chTBKfeT4A8QIbcZPx5tD7nt9eDF+9QR", + "Glyh8xFUeA8PCKMpidwd5Rv3RCCKwhaMAuOSeVCFTIDxQshgCUtfEHnySsCNwfM60s/kmlsSASfxtDfA", + "yxE/dgw0JVPqbYfqJ4B3KME1hjnGt7Gt5jzCOJoGreDG5Y6FQ+GoOxImnvGy8YBN1GZGqcoLUQXGiPSq", + "NacYh2PcoR589wI4GIXVdMeiCsfeRGOp5hZ1sQKb8aJIZSj6Gr8y/BpifWALed3UEmqCvLqppofU5ifK", + "lTT1Zs9cocEtp4vKnyeoIS7BHnYYE6YsdvhvqpDO+M5439ejgyuDo2txXH71YbBoSup1NJ0ZscqmYwLv", + "lNujo536ZoTe9r9TSg9Rl3+KoMoel4v3KMXfvnEXR5x/deBmTFdLkx4VXXoVfg95a5rEfl2uhFfZoFwU", + "Gq9x8xJb1gM+NEwCfsXLkYDmWOVN9yupgcfCmvPRKHxufZYly9leFjSauYZcPntK9KElaMzNk7w87075", + "7Ne6F6HjJpjvOwYXcvVpmcWooeVmtpB2g481hnx/NRbpHsot4Pd++ftL8EkxKw1XQtXBiSa4soYnIf3a", + "KSbf5BpIrj/pIP5HK59HVeVvfBlSWqZ/k3//MxnTGEird38Cxflg0weF9YfSLqmn2iasqWA3qaJd51ac", + "UookVfXCy4ad0v5dWhpUERmQ1fMp4sAAHx/ns/PiqAszVTllRqOkjt0LsVpbTLz+N+AF6FcHEsu3yeTx", + "iFXKiLaQZOkG85k81zjcyVSfcUfAIk6MPxwr+BJeQW6xemjrI6UBjkmT7yYLuvv/TjA//pxuXOt9Xvl9", + "yeSHJUMP3PGD/DdRDicqt3gyPXX6WeMJS4E819y0WTd6oa+TA/CWS8gxue3efEP/sQYZ5bKZB70MwrKM", + "0g+JJhwF0zMfr3VsAdqXDmgvPFGZlFuDMxaOfAm7e4Z1qCFZ/7GJxbpJ/lfEAHKHLKQCHlMke+cfYRrK", + "QCwEz06fUbetcTCaujfKnnXDuQJJuoujzai1Z8p07epJc7muR2Xvw8iKsZREw9K34++P51hp2Hg/J97k", + "j41f6ex8WP/k2uefxexQje0kZKIFE34LqeBollJcQlzcHi1V11wXocWd5Pahu0mkgV42M4vWD39oq05k", + "1MeQlrxUTozIxuKCuq7vjd/YPUMOfm0eFoRrCVpD0ZhESmUgsyr47e+DYx8qyIvxRkgwo1VsCLjRDMav", + "2xTNWM2LY8Zi7p0X4wUyDRvuoNNRIuXxOfch+xl9D7HUoZrTQQ1TQ6+Hy4qGCAxhBkiMqX7J/G15OEb7", + "JsomISXoLFie+lmVZTexFqZPLOqcLuj4YDQKuckpUPawkqSeJh+usvdGiGKdL2F3So+gUI817GAMNElO", + "BHqUN7K3yXeqfjMpuFd3At4fmw6sUqrMRowd58NU0H2KvxT5JWAqt8ZTeaTUNvsMdeyNNft6vQupj6sK", + "JBT3Txg7kxQbEgzb3SpxvcnlPbtv/i3OWtSUnd0r1U7eybSTPeZN17fkZmGY/TzMgGN1t5yKBjmQaHg7", + "koZa8+tE4fmTqa/yoam5Xwy8JSqCIiWTXJDF6hke9JTiCCPZo5QLaMjkzFu6mClVyiXzJtH2bqg0puLJ", + "ECALckrQdwOFHzyJgGR568QppAxmPneZWjINrRH5pknchpW4Uy/6/szNLF1+t1QaOjW1XW9K2NjEL4Ti", + "91wvhNVc726Sam1QCXygPRnF8kF3rMYTq11I6401xGFZqusMmVXWlCtIPW1dO9O9jEPtrLafO9ULiPy6", + "uPGC2o6tecFypTXkcY902B5BtVEaslKhm1fKAr20Tu7eYKyOZKVaMVXlqgAq+5GmoLG5aik5ik0QedUk", + "UUC0g0Gf1Cei44lT3lUZekrOQ4vOyJY54ngKxifj8RiixkN495RwP6rgxvkSNUICfV26sdckfcaF7OHI", + "OvaiLIPCYKyUPfvJ1OiOhIE3boonbKOM9S87Gsk0Q7UuXp/lSlqtyrKrBCKReOU12y/59izP7QulLhc8", + "v7yP70ipbLPSYh7CUvvOeO1MupeRaWLN/X6GU2qHrmmeSI4urO85x9H1sCMw3x/mWId13GeJOvi9dXWZ", + "V/rZcCYZt2oj8jQN/3N5t436pKVYQjLVE5Wko+B8bIaMOr4cGmcGZElDNIPkyZpaZ8zzNG/URebh/osS", + "b39ctgR/SYxcTEM+6aWWLB+VrXoAIKQUMWprTXXsYsmn4SpqRRHmaJLuAzqRi6Pnz+1gcyPcOVAWbgXU", + "wNuwAfAzeuzPKSUXeS4u1DZ8v9/m7LoR8B/3U3mHeYy5VF20pKXJqSrk9xjhCOnMwHv9j95gtPBiqhdS", + "U3N04o0aATDul9SBYZJ30rFgLLkoochSJevOG53QPHrZ+oiWfiVpYTwnz3kdKsa5sWsNPt8EidS6a2+q", + "uCMl1TQfam5lAVswmAyCyudzQ3aGYO+AkirF9R7fqspKuIKOu5ZPglGjaCeuIPQ1TWdWAFRo/evrpFJ+", + "SPFd3lNU+LVnkSfLFOwmNReEWNopdkAtkVSibGVGx8RMPUoOoitR1LyDP3OsyNFVu7mjnEDVQCbPwrtt", + "6jQ/0QivwwBnoX9KlAmYeD+NDx3NgtKo28eADvol1mbs1Mu0W2Kc4aUxaOBsRWP4JBJv+Yap+LUcVwAO", + "Sb593kzcJ6FkhNhvtpCjVNP1u7s9ThgOxkwve9OoCK6bHb65IvkPoeG9JDw6XuqpYQAZ7F5NTaALL7Bj", + "A6wdLJ3Y66RmrArn+b/nf3O2qMNA7l1NReriF9xzCBY7TCjdGCu8QCuaCy34F859PsH+o1xEntUbvmNK", + "4z/uvfaPmpdiucMTSuCHbsysuSMhbyIk27X3V3QT7xdM5gGwoBdQYSpat5g6ZjTczo0SAe2uwFBNRLEN", + "v4R4G9AsT5wnt47lmHqxEcbgZdfbziEW/OJDTogNL+I3Mmam69ZtDrlKXe//2UZtxVOFhFJVyfNQktDX", + "ROkoxKnsaCAuu4bN/rC+4fM4kEBTyrQlWh3CeYsbKPeO9NxI+cqP1XvogD0o8TgodXGrZRxTDb6NjN4T", + "EDlpKXe9C1P9QwZAx4XhDoEf18n7NPhPJo0cW8YU8P8seB+pjBnDS0UwPwGWOyH/CVhJr7pQ20zD0hxy", + "hSDFqnsI6zZZQFBOCplr4IZ8Q85/9E+2NieikO4JSd6LjfWtGaWApZAtsxSyqm3iBYCpEeUuQlisnka0", + "jhh7xqQEJ4Zd8fLHK9BaFGMb504H1ZCLc9IHlbzvm3j8N3fqcABh2tcPRhJCG6kWNXMXOFW9IcdCY7ks", + "uC7i5kKyHLS799k135mb2z4ctLp28sUB6wePpJlufHtkB0HSJkDKnTdf3tIy0QDI79BEMcG0gB6sCbMC", + "KUWsGrEkDGFIp1Xg26xUK4wvGyFAn3wSbT/0WFESFbYkDx03jxG/wf5pMO+2P/hW4axTpth/zn5E1OGD", + "5ycp7N6TRtq0fsAfeWTSQQj0L1etWzhtzpD+UzGabzCIoROnGYS7EMQQ9prcQ2g+GLFkdDW4I7uIBnIf", + "4Bura6fXM+ra4FORoPSGzfBta/Y4foNpnZx57h13hkqfwaOYkDL3cbRH6oRIkxzugRHwqPi0P1vdaRtn", + "CjfOMUWg9kfOZpWqsnyKNyCl5i+8QttD2oVxhD4idfXIuhvHCdMUq+gkNulUrTi2DtZo1YxDdpkq3/fI", + "HlNojHDQrrJcLZGXUWlm1MNgjEejvJj3o4+6CpuGSTDONOS1RoXmNd8dris0khL24m9nXzx6/MvjL75k", + "rgErxApMm1a4V5en9RgTsq9n+bQ+YoPl2fQmhLh0QlywlIVwm2ZT/FkjbmvanIGDqkTHaEITF0DiOCbq", + "wdxor3Cc1un7z7VdqUXe+Y6lUPD775lWZZlO696IbglVf2q3ImW/k/gr0EYY6xhh11YnbOsra9aojsPk", + "nleUZ0TJ3Gdfb6hA2BFnnNRCxlwtkZ9h1K+3bzDYVqXnVWST2Lcu/y4ijRg6Z6D/xgJYpSovSoslS0GE", + "sSU6irn0ikZ074y8JxtmS36UKUL0Pslp0osr4u7n9t1qjTbN6d0mJsSLcChvQJpjmvTxiPabcJJWlf6n", + "4R+JEP074xrNcn8PXpF8H9ys6vYk0Ibh2gnyQABG4jA7EXRxUf4206gmrTzq74Opsy9+vGxNoAcDBhCS", + "0OEAeHFgZduu8XH34PzBKTtfNkiJlvJ+jBI6yz8UqxlYb3ORRFvklRTWgiG2pIZiYRSIa5418a0jr5JB", + "GCxW4Hcv07JMhM+S3gTPVEw47kmgr3j56bnGt0Ibe4b4gOL1eNBMHEMZI5lQaW6Wwe0FnzR3FC95d1PL", + "Vxiy+x/g9ih5z/mhvLl4cJuh1gtLUq/CrUBRwOwaxyR3oEdfsoXPpl9pyIXpm6Gvg3DShAyCFkvveglb", + "eyBG8dA6f1b2FmS8DD4j7IfInKRQbddC2B7RP5ipjJzcJJWnqG9AFgn8pXhUXH3zwHVxy8zrN0sIEqX2", + "OjIhyLCu6NTlUdILd+nUBobrnHxbd3CbuKjbtU3NZjM5gfu7d2/tYkoSmnSyddcds+DcSdb1o3Ku/w75", + "bwhHfgw/b4pifh7LiEpZP0eS7/b2oxblQQeRTirlj/PZCiQYYTBZ8C++OMSnvUsDBBSTPzyqBOttEokQ", + "YhJr7UweTRUlSZ6QH9l3S2RDxni3vNbC7rAwaFCgiV+SmXq+a7I++Kwhje3K331WXUJTnLnNEVGbcLt+", + "p3iJ9xGZ1KS7hVR5wr7Z8k1VenUw++u9xb/B5395Ujz8/NG/Lf7y8IuHOTz54quHD/lXT/ijrz5/BI//", + "8sWTh/Bo+eVXi8fF4yePF08eP/nyi6/yz588Wjz58qt/u+f4kAOZAA25u5/O/k92Vq5UdvbqPHvjgG1x", + "wivxPbi9wbfyUmHhOofUHE8ibLgoZ0/DT/8rnLCTXG3a4cOvM1+AZba2tjJPT0+vr69P4i6nKwwKz6yq", + "8/VpmAfLiXXklVfnjTc5+b3gjrbaY9xUTwpn+O31Nxdv2Nmr85OWYGZPZw9PHp488rVrJa/E7Onsc/wJ", + "T88a9/3UE9vs6YeP89npGniJOVTcHxuwWuThkwZe7Pz/zTVfrUCfYMAA/XT1+DSIFacffHD8x33fTmOX", + "itMPnRwCxYGejctA0pj3QqlLtCUHQeee6TlAnMQ1dM8Lh0dqiV4L5rzlaKEQKhprZ0/fppQovsZTVS9K", + "kTO6h5EQHZYjOmkyQ7R8ADVmUc3/lqs5TvUw++r9hy/+8jElLfUBeekte60pw3uBYmAR+sSfBLj+UYPe", + "tYChmXsWgzG0+6UTZG0t1kKPZjthP3kXAfxKzCEEUIU4pCa3WOg0ApgbIgVXg4X3WI0LfeaQHB4/fBiO", + "sBeQI7I69dQao7trRBg41BwTsd4pUZuQbtxiMsTHkGJ/MpRVx2FTSE6O3JiPZMMvyXyCnmhM+9BIj1Hv", + "xopIbkIW/LYELv07Fh+ZEHdLMw2li49DtjdyAoMPaqzhKgXp77xfUKrK7Mf57MmR1LBX09RJEZkA/yUv", + "HchQhMwgBMGjTwfBuSRXSXd/0D33cT774lPi4Fw65sVLhi2jQpkJipeXUl3L0NIJJfVmw/UORQ47ZY99", + "Ihs0CoZ2RPd0Q3J3ht/OiC3P5jPYVqCFe/nxcvb+46Hr5fRDKJC8/zLqFMf1jr5Rh4mX3L5mpwssijS1", + "KZio8fhSUJdlTj/gCR39/dSr1NMfUStG4tZpyOM00pIydqQ/dlD4wW7dQvYP59pE4+Xc5uu6Ov2A/0HJ", + "KVoRJQA+tVt5il47px86iPCfB4jo/t52j1tcbVQBATi1XFJV6X2fTz/Qv9FEHcJshZqugPJN1OjZGvLL", + "Wfru62VHj3oxEiz5ooSCmNOTCR2ksnGnGx3o1yh+GPbj90wsGfSnECbMcMS5pdyRp1h7cdfiMvy8k3ny", + "x+E2d/Lmjfx8Gt41KdG22/JD58/ukTPr2hbqOpoFNYKkzh5C5j7Wpv/36TUX1r3xfbo2LNY87GyBl6e+", + "NkPv1zYd8uAL5niOfowDo5K/nnKP6lmlTIJsX/PryIx3ho1JQgBjv1b4ohi7nbbZQkikoPiGahUB9HEo", + "Gw/uJSfXoMdbsKUMU61gvgeteJFzg0WCfZmTgbT+MXnsPrW08TUvWEiTkbFW9jjzz83O0v5bEsHpP/90", + "01+AvhI5sDewqZTmWpQ79pNs4k5uzEi/ReLUPL9ECb0hWHKS1Py6G8qi02kIulV8QlYKYHbL1lwWpQ/c", + "VjUWAHeUhbZPFXnfuAsoVLGqlEYAKD0gFOSPYE7YReOtgb4PdXjkFHAFparQOIFJb2kSjp4cZM2LL4Iu", + "/5/Ptpk7xCuQmWcj2UIVO1/2Zab5td1SDPaAV5FwOMLIBqJb6quXTkYaBS/p8LnVEsZaN9QiNPq2t+/d", + "KxarQnsFQ6tEenp6imEza2Xs6cw9wrsKpvjj+wZhoZjhrNLiCrP1I9KUFu5tWWZeedMWvJo9Pnk4+/j/", + "AwAA///pfgaenwQBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index 435d296215..1755e868ee 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -251,6 +251,23 @@ type Account struct { // * lsig type AccountSigType string +// AccountAssetHolding AccountAssetHolding describes the account's asset holding and asset parameters (if either exist) for a specific asset ID. +type AccountAssetHolding struct { + // AssetHolding Describes an asset held by an account. + // + // Definition: + // data/basics/userBalance.go : AssetHolding + AssetHolding AssetHolding `json:"asset-holding"` + + // AssetParams AssetParams specifies the parameters for an asset. + // + // \[apar\] when part of an AssetConfig transaction. + // + // Definition: + // data/transactions/asset.go : AssetParams + AssetParams *AssetParams `json:"asset-params,omitempty"` +} + // AccountParticipation AccountParticipation describes the parameters used by this account in consensus protocol. type AccountParticipation struct { // SelectionParticipationKey \[sel\] Selection public key (if any) currently registered for this round. @@ -1091,6 +1108,17 @@ type AccountAssetResponse struct { Round uint64 `json:"round"` } +// AccountAssetsInformationResponse defines model for AccountAssetsInformationResponse. +type AccountAssetsInformationResponse struct { + AssetHoldings *[]AccountAssetHolding `json:"asset-holdings,omitempty"` + + // NextToken Used for pagination, when making another request provide this token with the next parameter. + NextToken *string `json:"next-token,omitempty"` + + // Round The round for which this information is relevant. + Round uint64 `json:"round"` +} + // AccountResponse Account information at a given round. // // Definition: @@ -1425,6 +1453,15 @@ type AccountApplicationInformationParams struct { // AccountApplicationInformationParamsFormat defines parameters for AccountApplicationInformation. type AccountApplicationInformationParamsFormat string +// AccountAssetsInformationParams defines parameters for AccountAssetsInformation. +type AccountAssetsInformationParams struct { + // Limit Maximum number of results to return. + Limit *uint64 `form:"limit,omitempty" json:"limit,omitempty"` + + // Next The next page of results. Use the next token provided by the previous results. + Next *string `form:"next,omitempty" json:"next,omitempty"` +} + // AccountAssetInformationParams defines parameters for AccountAssetInformation. type AccountAssetInformationParams struct { // Format Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON. diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 2ad021e012..568ebbb8ae 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -164,197 +164,199 @@ var swaggerSpec = []string{ "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", - "04A1VcMmT04eTT7+3wAAAP//lhM0FGMCAQA=", + "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", + "NYXFJk9OHk0+/t8AAAD//5YZK+EHBgEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 8c43f44310..815a346434 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -746,300 +746,302 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "V3SxYKoxIR49mT46evd/AwAA//+i458bR7YBAA==", + "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", + "IGsT4ujB5N7ow/8LAAD//5pnmKfruQEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 0d73a322b2..d64047fe99 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -203,225 +203,227 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 518cbf482e..409fd4ca65 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,231 +177,233 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "PwAA//+yhC5iNxkBAA==", + "H4sIAAAAAAAC/+x9f3fbtpLoV8HT7jlJvKLt/Gj3Nu/07HOSttfbpMmJ3e7ejfNaiBxJuCYBXgCUpebl", + "u7+DAUCCJChRtpq0u/0rsUgCg8FgML/nwyQVRSk4cK0mTz9MSippARok/kXTVFRcJywzf2WgUslKzQSf", + "PPXPiNKS8cVkOmHm15Lq5WQ64bSA5h3z/XQi4R8Vk5BNnmpZwXSi0iUU1AysN6V5ux5pnSxE4oY4s0Oc", + "v5h83PKAZpkEpfpQvub5hjCe5lUGREvKFU3NI0VumF4SvWSKuI8J40RwIGJO9LL1MpkzyDN17Bf5jwrk", + "Jlilm3x4SR8bEBMpcujD+VwUM8bBQwU1UPWGEC1IBnN8aUk1MTMYWP2LWhAFVKZLMhdyB6gWiBBe4FUx", + "efpuooBnIHG3UmAr/O9cAvwKiaZyAXryfhpb3FyDTDQrIks7d9iXoKpcK4Lv4hoXbAWcmK+OyatKaTID", + "Qjl5++1z8vjx46/MQgqqNWSOyAZX1cwersl+Pnk6yagG/7hPazRfCEl5ltTvv/32Oc5/4RY49i2qFMQP", + "y5l5Qs5fDC3AfxghIcY1LHAfWtRvvogciubnGcyFhJF7Yl8+6KaE83/WXUmpTpelYFxH9oXgU2IfR3lY", + "8Pk2HlYD0Hq/NJiSZtB3p8lX7z88nD48/fhP786S/3J/fvH448jlP6/H3YGB6ItpJSXwdJMsJFA8LUvK", + "+/h46+hBLUWVZ2RJV7j5tEBW774l5lvLOlc0rwydsFSKs3whFKGOjDKY0yrXxE9MKp4bNmVGc9ROmCKl", + "FCuWQTY13PdmydIlSamyQ+B75IbluaHBSkE2RGvx1W05TB9DlBi4boUPXNDvFxnNunZgAtbIDZI0FwoS", + "LXZcT/7GoTwj4YXS3FVqv8uKXC6B4OTmgb1sEXfc0HSeb4jGfc0IVYQSfzVNCZuTjajIDW5Ozq7xe7ca", + "g7WCGKTh5rTuUXN4h9DXQ0YEeTMhcqAckefPXR9lfM4WlQRFbpagl+7Ok6BKwRUQMfs7pNps+79fvP6B", + "CElegVJ0AW9oek2ApyKD7JiczwkXOiANR0uIQ/Pl0DocXLFL/u9KGJoo1KKk6XX8Rs9ZwSKrekXXrKgK", + "wqtiBtJsqb9CtCASdCX5EEB2xB2kWNB1f9JLWfEU97+ZtiXLGWpjqszpBhFW0PXXp1MHjiI0z0kJPGN8", + "QfSaD8pxZu7d4CVSVDwbIeZos6fBxapKSNmcQUbqUbZA4qbZBQ/j+8HTCF8BOH6QQXDqWXaAw2EdoRlz", + "us0TUtIFBCRzTH50zA2fanENvCZ0Mtvgo1LCiolK1R8NwIhTb5fAudCQlBLmLEJjFw4dhsHYdxwHLpwM", + "lAquKeOQGeaMQAsNllkNwhRMuF3f6d/iM6rgyydDd3zzdOTuz0V317fu+KjdxpcSeyQjV6d56g5sXLJq", + "fT9CPwznVmyR2J97G8kWl+a2mbMcb6K/m/3zaKgUMoEWIvzdpNiCU11JeHrFj8xfJCEXmvKMysz8Utif", + "XlW5ZhdsYX7K7U8vxYKlF2wxgMwa1qjChZ8V9h8zXpwd63VUr3gpxHVVhgtKW4rrbEPOXwxtsh1zX8I8", + "q7XdUPG4XHtlZN8v9LreyAEgB3FXUvPiNWwkGGhpOsd/1nOkJzqXv5p/yjI3X+tyHkOtoWN3JaP5wJkV", + "zsoyZyk1SHzrHpunhgmAVSRo88YJXqhPPwQgllKUIDWzg9KyTHKR0jxRmmoc6Z8lzCdPJ/900thfTuzn", + "6iSY/KX56gI/MiKrFYMSWpZ7jPHGiD5qC7MwDBofIZuwbA+FJsbtJhpSYoYF57CiXB83KkuLH9QH+J2b", + "qcG3lXYsvjsq2CDCiX1xBspKwPbFe4oEqCeIVoJoRYF0kYtZ/cP9s7JsMIjPz8rS4gOlR2AomMGaKa0e", + "4PJpc5LCec5fHJPvwrFRFBc835jLwYoa5m6Yu1vL3WK1bcmtoRnxniK4nUIem63xaDBi/iEoDtWKpciN", + "1LOTVszLf3XvhmRmfh/18R+DxELcDhMXKloOc1bHwV8C5eZ+h3L6hOPMPcfkrPvt7cjGjLKFYNR5g8VD", + "Ew/+wjQUaiclBBAF1OS2h0pJNxMnJCYo7PXJ5EcFlkJKumAcoZ0a9YmTgl7b/RCId0MIoGq9yNKSlSBr", + "E6qTOR3qj3t2lj8AtcY21kuiRlLNmdKoV+PLZAk5Cs6Ue4IOSeVWlDFiw7csoob5RtLS0rJ7YsUuxlGf", + "ty9ZWO948Y68E6MwB+w+2GiE6tZseSfrjEKCXKMDw7NcpNd/pWp5gBM+82P1aR+nIUugGUiypGoZOTgd", + "2m5GG0Pf5kWkWTILpjqul/hSLNQBlpiLfVhXWT6neW6m7rOszmpx4FEHOc+JeZlAwdBg7hRHa2G3+hf5", + "hqZLIxaQlOb5tDEViTLJYQW5UdoZ5yCnRC+pbg4/juz1GjxHCgyz00CC1TgzE5rYZG2LkEAKijdQYbSZ", + "Mm9/U3NQRQvoSEF4I4oKrQiBonH+wq8OVsCRJ9VDI/j1GtFaEw5+bOZ2j3BmLuzirAVQe/ddjb+aX7SA", + "Nm839ylvphAyszZrbX5jkqRC2iHsDe8mN/8BKpuPLXXeLyUkbghJVyAVzc3qOot6UJPvoU7njpOZUU2D", + "k+moMK6AWc6B36F4BzJipXmN/6E5MY+NFGMoqaEehsKICNypmb2YDarsTOYFtLcKUlhTJilper0XlM+b", + "yeNsZtTJ+8ZaT90WukXUO3S5Zpk61DbhYEN71T4h1nbl2VFPFtnKdIK5xiDgUpTEso8OCJZT4GgWIWJ9", + "8GvtmVjHYHom1r0rTazhIDthxhnN7J+J9QsHmZC7MY9jj0G6WSCnBSi83XjIOM0sjV/ubCbk7aSJzgXD", + "SeNtJNSMGghT0w6S8NWqTNzZjHgs7AudgZoAj+1CQHf4GMZaWLjQ9DfAgjKjHgIL7YEOjQVRlCyHA5D+", + "MirEzaiCx4/IxV/Pvnj46OdHX3xpSLKUYiFpQWYbDYrcd2Y5ovQmhwdR7Qili/joXz7xPqr2uLFxlKhk", + "CgUt+0NZ35fVfu1rxLzXx1obzbjqGsBRHBHM1WbRTqxb14D2gimjOxWzg2zGEMKyZpaMOEgy2ElM+y6v", + "mWYTLlFuZHUIswBIKWT06iql0CIVeWLkIyYiiv0b9wZxb3jLRtn93UJLbqgiZm70+lU8G9Df9ZqP5/t2", + "6Ms1b3CzlfPb9UZW5+Ydsy9t5DfSewky0WtOMphVi5ZZYS5FQSjJ8EO8o78DbeUWVsCFpkX5ej4/jJVQ", + "4EAR+wcrQJmZiH3DSA0KUsFtENwOU4cbdQx6uojx3hk9DIDDyMWGp+hiOsSxHbYCFYyjv1tteBqYhAyM", + "OWSLFlne3fQzhA471T0VAceg4yU+Rhv3C8g1/VbIy0bs+06Kqjy4kNedc+xyqFuMs6Jn5ltvPmV8kbcD", + "LxcG9uPYGj/Lgp7XyrddA0KPFPmSLZY60LPeSCHmh4cxNksMUHxgjSy5+aZvavlBZIaZ6EodQARrBms4", + "nKHbkK/Rmag0oYSLDHDzKxUXzgZC9TBGCEObdCjvoV7PFJmBoa6UVma1VUkwcKd3XzQfJjS1JzRB1KiB", + "sIU63sS+ZaezYWC5BJptyAyAEzFzsQEuagEXSTHqSHvxxomGEX7RgquUIgWlIEucCXcnaP49e3XoLXhC", + "wBHgehaiBJlTeWdgr1c74byGTYIxcorc//4n9eAzwKuFpvkOxOI7MfR27VB9qMdNv43gupOHZGctXJZq", + "iRYozeagYQiFe+FkcP+6EPV28e5oWYHEUIzflOL9JHcjoBrU35je7wptVQ5Efjv11kh4ZsM45cILVrHB", + "cqp0sostm5daOrhZQcAJY5wYBx4QvF5SpW34EOMZ2gLtdYLzWCHMTDEM8KAaYkb+yWsg/bFTcw9yVala", + "HVFVWQqpIYutAT2Zg3P9AOt6LjEPxq51Hi1IpWDXyENYCsZ3yLIrsQiiuvZbOk9of3Hoizb3/CaKyhYQ", + "DSK2AXLh3wqwG0a/DgDCVINoSzhMdSinDrmdTpQWZWm4hU4qXn83hKYL+/aZ/rF5t09c1jlg7+1MgELH", + "g3vfQX5jMWvjnpdUEQeHd02jGcTGOfVhNocxUYynkGyjfFTxzFvhEdh5SKtyIWkGSQY53USc6vYxsY+3", + "DYA73qi7QkNiA1jjm95Qso8X3DK0wPFUTHgk+ISk5ggaVaAhEPf1jpEzwLFjzMnR0b16KJwrukV+PFy2", + "3erIiHgbroQ2O+7oAUF2HH0MwAN4qIe+PSrw46TRPbtT/A2Um6CWI/afZANqaAnN+HstYMCG6nKDgvPS", + "Ye8dDhxlm4NsbAcfGTqyAwbdN1RqlrISdZ3vYXNw1a87QdThTDLQlOWQkeCBVQPL8HtiQy+7Y95OFRxl", + "e+uD3zO+RZbjw1vawF/DBnXuNzamPzB1HEKXjYxq7ifKCQLqI4WNCB6+Amua6nxjBDW9hA25AQlEVTPr", + "+u/7IbQok3CAqF9jy4zOqxn1KW51s17gUMHyYjFaVifYDt9lRzFoocPpAqUQ+QgLWQ8ZUQhGxVyQUphd", + "Zy5tyCeOeEpqAemYNrq06+v/nmqhGVdA/iYqklKOKleloZZphERBAQVIM4MRweo5XVBfgyHIoQCrSeKT", + "o6Puwo+O3J4zReZw43PtzItddBwdoR3njVC6dbgOYA81x+08cn2gw8dcfE4L6fKU3ZFCbuQxO/mmM3jt", + "JTJnSilHuGb5d2YAnZO5HrP2kEbGRUnhuKN8Oe24mt66cd8vWFHlVB/CawUrmidiBVKyDHZycjcxE/yb", + "Fc1f159hHiGkhkZTSFLMfhs5Flyab2zCnBmHcWYOsA2WHwsQnNuvLuxHO1TMJsKTFQVkjGrIN6SUkILN", + "EzOSo6qXekxsBHm6pHyBCoMU1cIFhdpxkOFXyppmZMV7Q0SFKr3mCRq5YxeAC+/yqYJGnAJqVLquhdwq", + "MDe0ns9lh465mYM96HoMok6y6WRQ4zVIXTUar0VOO99xxGXQkvcC/DQTj3SlIOqM7NPHV7gt5jCZzf1t", + "TPbN0DEo+xMHkbLNw6FgWaNu55sDCD12ICKhlKDwigrNVMo+FfMwt9mH2G2UhqJvybef/jxw/N4O6ouC", + "54xDUggOm2g5D8bhFT6MHie8Jgc+RoFl6NuuDtKCvwNWe54x1HhX/OJud09o12OlvhXyUC5RO+Bo8X6E", + "B3Knu91NeVs/Kc3ziGvRZT52GYCa1kGuTBKqlEgZymznmZq6aFrrjXRpkm30v6nzOQ5w9rrjdnxoYVI9", + "2oghLwklac7Qgiy40rJK9RWnaKMKlhoJfvLK+LDV8rl/JW4mjVgx3VBXnGLgW225igZszCFipvkWwBsv", + "VbVYgNIdXWcOcMXdW4yTijONcxXmuCT2vJQgMQLp2L5Z0A2ZG5rQgvwKUpBZpdvSPyb2Ks3y3Dn0zDRE", + "zK841SQHqjR5xfjlGofzTn9/ZDnoGyGvayzEb/cFcFBMJfEgre/sU4yHd8tfuth4DBO3j32wZlNpYGKW", + "2Sou8n/v/9vTd2fJf9Hk19Pkq385ef/hyccHR70fH338+uv/1/7p8cevH/zbP8d2ysMeSzt1kJ+/cJrx", + "+QtUf4IQ9y7sn8z+XzCeRIksjObo0Ba5jyUWHAE9aBvH9BKuuF5zQ0grmrPM8JbbkEP3humdRXs6OlTT", + "2oiOMcyvdU+l4g5chkSYTIc13lqK6sc1xhO80SnpcrbxvMwrbrfSS982f9HHl4n5tE7it/W9nhLM8F5S", + "Hxzp/nz0xZeTaZOZXT+fTCfu6fsIJbNsHcu/z2Ad0xXD5IJ7ipR0o0DHuQfCHg2ls7Ed4bAFFDOQasnK", + "T88plGazOIfzqT7O5rTm59wGxpvzgy7OjfOciPmnh1tLgAxKvYzV/WkJavhWs5sAnbCTUooV8Clhx3Dc", + "tflkRl90QX050LlPf5FCjNGG6nNgCc1TRYD1cCGjDCsx+umkBbjLXx1cHXIDx+Dqzln7M/3fWpB7331z", + "SU4cw1T3bCkIO3SQvB9RpV3SYSsgyXCzMBfril/xFzBH64PgT694RjU9mVHFUnVSKZDPaE55CscLQZ76", + "PMYXVNMr3pO0BgsSBsnGpKxmOUvJdaiQNORpi0z1R7i6ekfzhbi6et+LzeirD26qKH+xEyRGEBaVTlyJ", + "nETCDZUx35eqS6TgyLYG1rZZrZAtKmsg9SV43PhxnkfLUnVLJfSXX5a5WX5AhsoVAjBbRpQWdR6XEVBc", + "KqzZ3x+EuxgkvfF2lUqBIr8UtHzHuH5Pkqvq9PQxZsQ1tQN+cVe+oclNCaOtK4OlHLpGFVy4VSthrSVN", + "SrqIudiurt5poCXuPsrLBdo48pzgZ61sPR+Yj0M1C6hTgwc3wMKxd1ItLu7CfuXLIcaXgI9wC9uJy3fa", + "ryDv/NbbtSN3nVZ6mZizHV2VMiTud6aukrYwQpaPxlBsgdqqKyg3A5IuIb12lb6gKPVm2vrcB/w4QdOz", + "DqZsDTibmYdViNBBMQNSlRl1ojjlm245GAVa+7Dit3ANm0vRFDHap/5LuxyJGjqoSKmBdGmINTy2bozu", + "5ruoMp+g6ap6YNKjJ4unNV34b4YPshV5D3CIY0TRKpcxhAgqI4iwxD+Aglss1Ix3J9KPLY/xFLhmK0gg", + "Zws2i5Wv/Y++P8zDaqjSVexzUcj1gIqwOTGq/MxerE69l5QvwFzP5koViua2Gmk0aAP1oSVQqWdA9VY7", + "Pw8LOXjoUKW8wYxltPBNzRJgbfababTYcbgxWgUaiuw7Lnr5eDj+zAIO2S3h8Z83msLxoK7rUBep1Odv", + "5Rq7tVrrQvNCOkO47PMCsNSnuDH7YqAQrkqlLYYS3C+VogsY0F1C793IOhItjx8OsksiicogYt4VNXqS", + "QBRk+3Ji1hw9w2CemEOMamYnINPPZB3EzmeExacdwmY5CrB15KrdeypbXlRbTXcItDhrAckbUdCD0cZI", + "eByXVPnjiHVGPZcdJZ39huVStpV0Ow9iCYNionXBNn8bdjloT+93hd18NTdfwi1U+keUYzO6F6YvxLZD", + "cBRNM8hhYRduX/aE0hQaajbIwPF6PkfeksTCEgMDdSAAuDnAaC5HhFjfCBk9QoyMA7Ax8AEHJj+I8Gzy", + "xT5Aclcoifqx8YoI/oZ4Yp8N1DfCqCjN5coG/I2p5wCuhEMjWXQiqnEYwviUGDa3orlhc04XbwbpVRZD", + "haJTR8yF3jwYUjS2uKbslb/XmqyQcJvVhNKsBzouam+BeCbWic3sjeois/XM0Hs0dwHzjGMH09Zwu6fI", + "TKwxnAuvFhsrvwOWYTg8GIHtZc0U0it+NyRnWWC2Tbtdzo1RoUKScYbWmlyGBL0xUw/IlkPkcj8oy3Yr", + "ADpmqKbHgTNL7DQftMWT/mXe3GrTptyoTwuLHf+hIxTdpQH89e1j7UJqf20K5g0X5fIn6pNUkOtblu5S", + "2c9+XNpqffsU9uuSQwuILVh905UDo2htx3q18RpgLcZKDPPtOyX7aFOQAyrBSUs0Ta5jkQJGlwe8xy/8", + "Z4GxDneP8s2DIIBQwoIpDY3TyMcFfQ5zPMWyw0LMh1enSzk363srRH35W7c5ftha5idfAUbgz5lUOkGP", + "W3QJ5qVvFRqRvjWvxiXQdoiiLdLPsjjHxWmvYZNkLK/i9Orm/f6FmfaH+qJR1QxvMcZtgNYMm0pEA5e3", + "TG1j27cu+KVd8Et6sPWOOw3mVTOxNOTSnuMPci46DGwbO4gQYIw4+rs2iNItDDJIOO9zx0AaDWJajrd5", + "G3qHKfNj74xS82nvQze/HSm6lqB8XjxDUCwWkPmyYN4fxoPia7ngi6D7UVluqzV3TGzJN6zYtqXYmwvD", + "h6Eg/EDcTxjPYB2HPtQKEPImsw4L1eEkC+C2XEncLBRFTRjij28EtrpP7AvtJgBEg6AvO87sJjrZ7lK9", + "nbgBOdDM6SQK/Pq2H8v+hjjUTYfCp1sVQ7cfIRwQaYrpoCFIvwzBAAOmZcmydcfxZEcdNILRvazLA9IW", + "shY32A4MtIOgowTXKkHtQq2dgf0Edd4To5XZ2GsXWGzom6YuAT+rJHowWpHN/Xrnta42cu3f/3ShhaQL", + "cF6oxIJ0pyFwOfugIagmrohmNpwkY/M5hN4XdRvPQQu4no09G0G6ESKLu2gqxvWXT2JktIN6Ghh3oyxO", + "MRFaGPLJX/a9XF6mD0xJ9ZUQbM0tXFXRdP3vYZP8RPPKKBlMqiY817md2pfvHru+Kr6HDY68M+rVALZj", + "V9Dy9BaQBmOW/vqRCgo/31Ot0vioXra2cI+dOovv0oG2xjUzGCb+5pZpFftvL+UuB6MJkjCwjNmNi3hs", + "gjk90EZ8l5R3bQLLdssggbwfTsWUb/3Yv4rqWhS7aPcSaO6JF5cz+Tid3C0SIHabuRF34PpNfYFG8YyR", + "ptYz3Ars2RPltCylWNE8cfESQ5e/FCt3+ePrPrziE2syccq+/Obs5RsH/sfpJM2ByqS2BAyuCt8r/zCr", + "su0Ptl8ltkq2M3RaS1Gw+XUl4zDG4gYrYneMTb1mIk38THAUXczFPB7wvpP3uVAfu8QtIT9Q1hE/jc/T", + "Bvy0g3zoirLcOxs9tAPB6bi4cR1polwhHODOwUJBzFdyUHbTO93x09FQ1w6ehHO9xtKUcY2Du8KVyIpc", + "8A89uPT0rZAt5u8yE6PBQ7+dWGWEbIvHgVht3/exK0wdEyt4/bL4xZzGo6PwqB0dTckvuXsQAIi/z9zv", + "qF8cHUW9h1EzlmESaKXitIAHdZbF4EZ8WgWcw824C/psVdSSpRgmw5pCbRSQR/eNw96NZA6fmfslgxzM", + "T8djlPRw0y26Q2DGnKCLoUzEOsi0sK0mFRG8G1ONSbCGtJDZu1YG1hnbP0K8KtCBmaicpfHQDj5Thr1y", + "G0xpXib48oC11oxYsYHYXF6xYCzz2piaqR0ggzmiyFTRsq0N7mbCHe+Ks39UQFhmtJo5A4n3Wueq88oB", + "jtoTSON2MTew9VM1w9/FDrLF3+RtQduMIFv9dy9qn5JfaKxZzp4R4OGMPca9JXrb0YejZpvNtmyHYI7T", + "Y8a0HPeMzjnrBuaIthBnKplL8SvEHSHoP4oUwvCOT4Zm3l+BxyL3uiyldio3ndCb2Xdt93jdeGjj76wL", + "+0XX3bpuc5nGT/V+G3kbpVfFyzU7JA8pYWGEQTs1YIC14PEKgmGxfYiPPqLcnidbBaKVYRY/lWEu54kd", + "vzmVDuZe/mtOb2Y01lvF6EIGpmB7W3FSWhD/sd8AVdc4sLOTIIK7fpfZSnIlyMYH0a9Ke0u9xk47WqNp", + "FBikqFB1mdowhVyJyDAVv6Hcdt8231l+5b5WYF3w5qsbIbEOpIqHdGWQsiJqjr26epel/fCdjC2YbSxd", + "KQg6F7uBbNN+S0Wu+3NducOh5nxOTqdB+3S3GxlbMcVmOeAbD+0bM6rwuqzd4fUnZnnA9VLh649GvL6s", + "eCYh00tlEasEqXVPFPLqwMQZ6BsATk7xvYdfkfsYkqnYCh4YLDohaPL04VcYUGP/OI3dsq4x+DaWnSHP", + "9sHacTrGmFQ7hmGSbtR49PVcAvwKw7fDltNkPx1zlvBNd6HsPksF5XQB8fyMYgdM9lvcTXTnd/DCrTcA", + "lJZiQ5iOzw+aGv40kPNt2J8Fg6SiKJguXOCeEoWhp6YtsZ3UD2d75Ls+Sx4u/xDjX0sf/texdX1iNYYW", + "AzlbGKX8A/poQ7ROCbXFP3PWRKb7Ppfk3NcWxsZTdb8pixszl1k6ypIYqD4npWRco/2j0vPkL0YtljQ1", + "7O94CNxk9uWTSAOndo8Tvh/gnxzvEhTIVRz1coDsvcziviX3ueBJYThK9qCpsRCcysFA3XhI5lBc6Pah", + "x0q+ZpRkkNyqFrnRgFPfifD4lgHvSIr1evaix71X9skps5Jx8qCV2aEf3750UkYhZKxhQHPcncQhQUsG", + "K8yYi2+SGfOOeyHzUbtwF+g/b/yTFzkDscyf5agiEHg0tyXLGyn+p1dN5XN0rNpMxI4NUMiItdPZ7T5x", + "tOF+Vreu/9YGjOGzAcyNRhuO0sfKQPS9Da+vv/kc8UJdkOyetwyOD38h0ujgKMcfHSHQR0dTJwb/8qj9", + "2LL3o6N4AeKoyc382mDhLhoxfhvbw2ciYgDz3f7qgCJXHyFigBy6pMwDwwRnbqgpaXdW+/RSxGHyu+LR", + "pvFTcHX1Dp94POAfXUR8ZmaJG9hkKQwf9nZnySjJZPXzIM6dkmdiPZZwOneQJ57fAYoGUDLSPIcr6XXO", + "jLrrd8aLBDRqRp1BLoySGTYFCu35fxw8m8VPt2C7Ynn2U1PbrXORSMrTZTRKeGY+/NnK6K0r2LLKaJ+R", + "JeUc8uhwVrf92evAES3972LsPAXjI9/tdm61y+0srgG8DaYHyk9o0Mt0biYIsdoum1WXZcgXIiM4T9PU", + "omGO/RbIQV/Gf1SgdOxo4AObgIjOLsN8bVtAAjxD69cx+Q4L2BhYWhXL0erka8G26yJWZS5oNsUatZff", + "nL0kdlb7je2gbdsSLtDo0l5F1Eq+R591Z3QeKICyT7/2bRUZzKqVTuougrESc+aNps8h64ROoDkmxM4x", + "eWEtYXX/cjsJwUrHsoAsaFpodTGkCfMfrWm6RBNT6yIbJvnx/TQ9VTYG+CCJrG5ig+fOwO1aatqOmlMi", + "9BLkDVOAidWwgnZVu7rEozNx+ip37eXJinNLKcd7yBR1y5p90e6BswKJ9w1HIesgfk8Dg21Hu2970Qv8", + "Kh5S3+lV2nHe+hppddP1V85GnFIuOEuxon1MIMIKXOO8TSOK/8fdRGriTmjkcEU7pNYpnQ6Lgz1TPSN0", + "iOt7boOnZlMtddg/Naxd56wFaOU4G2RT3+jX+TUYV+CaEhkiCvmkkJHYlGg8e+0H35OMsLjOgKHqW/Ps", + "B2fGxNoG14yjwcKhzYnZ1vOQK4YORk6YJgsByq2nnZSh3plvjrHYXgbr98cvxYKlF2yBY9hoKLNsG/rX", + "H+rMBwK6wDvz7nPzriuBXv/ciuqxk56VpZt0uA10vPf9mg8iOBZ+4uMBAuTW44ejbSG3rRG8eJ8aQoMV", + "Bh9BifdwjzDqlsjtUb4xKoKlKHyD2MS4aB1UxiNgvGTce8LiF0QavRJwY/C8DnynUkm1FQFH8bRLoPlA", + "HDsmmlpX6l2H6haANyjBNfo5hrex6eY8wDjqFxrBjfIN8YfCUHcgTDyneR0BG+nNjFKVE6IyzBHpdGuO", + "MQ7DuH0/+PYFsDMLq/4cmyrsexMNlZqbVdkCdEKzLFah6Bk+JfjU5/rAGtKq7iVUJ3m1S033qc1NlAqu", + "qmLLXP6FO04XtD+PUEPYgt3vMBZMmW3w31gjneGdcbGveydX+kDXbL/66v1k0ZjUa2g6UWyRjMcE3il3", + "R0cz9e0Ivfn+oJTusy5/F0mVHS4X7lGMv31jLo6w/movzNheLXV5VAzpFfjc162pC/u1uRJeZb12Uei8", + "xs2LbFkHeP9iFPAVzQcSmkOTt71frRl4KK05HczCp9pVWdKUbGVBg5VrbMhnx4je9wQNhXnaKM/DGZ/d", + "WrcidNgF833L4WJDfRpmMehouZ0vpNngfZ0h36+GMt19uwV83m1/fw2uKGYpYcVE5YNofCirVwntr61m", + "8nWtgej6owHin9v4PGgqv3RtSO0ynU7+/U/WmUaAa7n5HRjOe5vea6zfl3ateap5hdQd7EZ1tGvdimNa", + "kcS6XjjZsNXav01LvS4iPbJ6MUYc6OHj43Rynu11YcY6p0zsKLFj95ItlhoLr/8VaAbyzY7C8k0xeTxi", + "pVCsaSSZm8FcJc8lDnc8NmbcEDALC+P3x/KxhCtINXYPbWKkJMA+ZfLNZN52/2eB+WF1ug6td3XltxWT", + "77cM3XHH9+rfBDWcbLvF4/Gl08/qSFibyHNDVVN1o5P6OjoBbz6HFIvbbq039B9L4EEtm6m3yyAs86D8", + "EKvTUbA88/5WxwagbeWAtsITtEm5MzhD6cjXsLmnSIsaov0f61ys29R/RQwgd0h8KeAhQ7IL/mGqpgzE", + "go/sdBV1mx4Hg6V7g+pZt5zLk6S5OJqKWlumjPeuHjWX+XSv6n2YWTFUkqjf+nZY/3iBnYaVi3Oidf3Y", + "UEsn5/3+Jzeu/ixWh6p9J74SLSj/my8FZ2fJ2TWEze3RU3VDZebfOEhtH3s3sTjQ83pm1sTh933VkYr6", + "mNKS5sKIEclQXlA79L2OG7unbIBfU4cF4ZqDlJDVLpFcKEi08HH72+DYhgobxXgrJKjBLjYWuMEKxm+b", + "Es3YzYtixWLqghfDBRIJBTXQyaCQ8vCc25D93D73udS+m9NOC1NNr7vbivoMDKZ6SAypfk7cbbk7R/s2", + "xibGOcjEe566VZV5u7AWlk/MqtRe0OHBqA1yo0ugbGElUTtN2l9lR0cIcp2vYXNilSDfj9XvYAi0lZws", + "6EHdyM4mH9T8pmJwLw4C3uctB1YKkScDzo7zfinoLsVfs/QasJRbHak80Gqb3Ecbe+3NvllufOnjsgQO", + "2YNjQs64zQ3xju12l7jO5Pye3jb/GmfNKlud3RnVjq94PMge66bLO3IzP8x2HqbAsLo7TmUH2VFoeD1Q", + "hlrSm0jj+eOxWnnf1dxtBt4QlYUiJpNcWI/VczzoMcMRZrIHJRfQkUmJ83QRlYtYSOZtsu3NUHFMhZMh", + "QBr4mKTvGgo3eBQB0fbWkVNoK5i52mViTiQ0TuTbFnHrd+KOafTdmetZ2vxuLiS0emqbr23Bxjp/wTe/", + "p3LGtKRyc5tSa71O4D3rySCWd4Zj1ZFYzUKaaKw+DvNc3CTIrJK6XUFMtTXvqfZl7HtnNd+ZUz2DIK6L", + "KieobciSZiQVUkIafhFP27NQFUJCkgsM84p5oOfayN0F5upwkosFEWUqMrBtP+IUNDRXxTlFsQmCqJoo", + "CiztYNKn/Sag45FTHqoNvS3OYxedWF/mQOApKFeMx2HIvtyHd0sL970abpzP0SLEMNalnXttpc+wkT3s", + "2cee5bk3GAy1sic/qgrDkTDxxkzxhBRCaafZ2ZFUPVQT4nU/FVxLkedtI5AViRfOsv2Krs/SVL8U4npG", + "0+sHqEdyoeuVZlOfltoNxmtmkp2KTCN77ncrnNr3MDTNEcnejfUd59i7H3YA5vvdHGu3jfss0ge/s642", + "84qrDWecUC0KlsZp+I8V3TYYkxZjCdFST7YlnU3Ox9eQUYeXQx3MgCypj2bgNNpT64w4nuacusg8zH9R", + "4u2OS+bgLomBi6nPJ53UkqSDslUHAITUZozqSto+dqHkU3MVsbAZ5uiS7gI6kotj5M/dYDMjHBwoDXcC", + "qhdtWAN43yr7U1uSy0YuzsTaP3/Q1Oy6FfAft1N5i3kMhVRdNKQlbVCVr+8xwBHilYG3xh9dYrbwbGwU", + "Ut1zdOSNGgAwHJfUgmFUdNK+YMwpyyFLYi3rzmub0DTQbF1GS7eTNFOOk6e08h3jzNiVBFdvworUsu1v", + "KqkhJVG/3rfc8gzWoLAYhG2fT5X1M3h/B+S2U1xH+RZlksMKWuFarghGhaIdW4H/VtUfkwygRO9f1yYV", + "i0MK7/KOocKtPQkiWcZgN2q5sIi1O0V2mCWiRpQ1T+wxUWOPkoFoxbKKtvCn9hU52mY3c5QjqOrJ5InX", + "28ZO86Md4a0f4Mx/HxNlPCbej+NDe7OgOOq2MaCdcYmVGjr1PB6WGFZ4qR0aOFtWOz4tiTd8Q5X0hg8b", + "APsk36g3I/eJCR4g9ps1pCjVtOPu7o4TgoMR1aneNCiCy3qHb29I/iw0vJWEB8eLqRoKkMFutdR4unAC", + "O76AvYO5EXuN1Ixd4Rz/d/xvSmaVH8jo1bZJXajBvQDvscOC0rWzwgm0rL7QfHzh1NUT7CrlLIisLuiG", + "CIn/GH3tHxXN2XyDJ9SC7z8jakkNCTkXofVdu3hFM/F2wWTqAfN2AeGnsutmY8cMhtuYUQKgzRXou4kI", + "UtBrCLcB3fKW86TasBxVzQqmFF52ne3sY8Et3teEKGgW6shYma7dt9nXKjVf/+8mayucyheUKnOa+paE", + "ridKyyBu24564tJLKLan9fXVY08CdSvThmilT+fNbmHc2zNyIxYrP9TvoQV2r8Vjr9XFnZaxTzf4JjN6", + "S0LkqKUcehfGxof0gA4bw+0CP+yT92nwHy0aObSMMeD/XvA+0BkzhNc2wfwEWG6l/EdgtXbVmVgnEuZq", + "VyiENawaRVg2xQK8cZLxVAJVNjbk/LVT2ZqaiIwbFdJGL9bet3qUDOaMN8yS8bLSEQ0ASyPyTYCw0DyN", + "aB1w9gxJCUYMW9H89QqkZNnQxpnTYXvIhTXpvUnefRtR/us7tT8AU432g5mE0GSqBa+ZC9x2vbGBhUpT", + "nlGZha8zTlKQ5t4nN3Sjbu/7MNDKysgXO7wfNJBm2vntgR8ESdsCkm+c+/KOnokaQHpAF8UI1wJGsEbc", + "CtYoosWAJ6EPQ7ysAl0nuVhgftkAAbrik+j7scqK4GiwtfLQfvMo9itsnwbrbruDrwXOOmaK7efsNaIO", + "FZ4fOdNbT5q1pnUT/mxEpj0Inv75ogkLt5vTp/9YjuYlJjG08jS9cOeTGPxe2/AQOx8MeDLaFtyBXUQH", + "uUvwDc214/sZtX3wsUxQq8MmqNuqLYHfoJogZ5q6wJ2+0aenFFukTF0e7Z42IWtJ9vfAAHi2+bQ7W+1p", + "62AKM84+TaC2Z84mpSiTdEw0oC3NnzmDtoO0DeMAfQTm6oF114ETqm5W0Sps0upasW8frMGuGbv8MmW6", + "TckeMmgMcNC2sVzMkZfZ1sxoh8Ecj9p4Me1mH7UNNjWTIJRISCuJBs0butndV2igJOzFX8++ePjo50df", + "fEnMCyRjC1BNWeFOX54mYozxrp3l08aI9Zan45vg89It4rynzKfb1JvizprltqqpGdjrSrSPJTRyAUSO", + "Y6QfzK32Csdpgr5/X9sVW+TBdyyGgt9+z6TI83hZ91p0i5j6Y7sVGPuNxF+CVExpwwjbvjqmm1hZtURz", + "HBb3XNk6I4Knrvp6TQVMDwTjxBYyFGqJ/Ayzfp1/g8C6zB2vsj6JbetyepG1iGFwBsZvzICUonSiNJuT", + "GESYWyKDnEtnaMTwziB6sma2No4yRoguJjlOemFH3O3cvt2tUcc5vdnEiHjhD+UtSHPIkj6c0X4bTtKY", + "0n83/COSon8wrlEv97fgFVH94HZdt0eB1k/XjpAHAjCQh9nKoAub8jeVRqW1yqP93rs6u+LHq8YFujNh", + "ACHxH+wAL0ysbN6rY9wdOJ+5ZOerGinBUt4PUUJr+btyNT3rrS+SYIuckUJrUJYtib5YGCTiqud1fuuA", + "VtJLg8UO/EYzzfNI+qy1m+CZCgnHqARyRfNPzzW+ZVLpM8QHZG+Hk2bCHMoQyRaV6nYV3F7SUXMH+ZKH", + "m5q/wZTd/wCzR9F7zg3l3MW92wytXtiSeuFvBZsFTG5wTBsO9PBLMnPV9EsJKVNdN/SNF07qlEGQbO5C", + "L2Gtd+Qo7lrnT0LfgYznPmaE/BC4kwSa7RoImyP6mZnKwMmNUnmM+npkEcFfjEeF3Td3XBd3rLx+u4Ig", + "QWmvPQuC9PuKjl2eLXphLp1KQX+do2/rFm4jF3WztrHVbEYXcL+6eqdnY4rQxIutm8+xCs5Bqq7vVXP9", + "N6h/Y3HkxnDzxijmp6GKqLbq50Dx3c5+VCzfGSDSKqX8cTpZAAfFFBYL/tk1h/i0d6mHwObk94+qhfUu", + "hUQsYiJrbU0eTBUUSR5RH9l9FqmGjPluaSWZ3mBjUG9AYz9HK/V8V1d9cFVDat+Vu/u0uIa6OXNTI6JS", + "/nb9TtAc7yPrUuPmFhL5MflmTYsyd+Zg8vW92b/C4788yU4fP/zX2V9OvzhN4ckXX52e0q+e0IdfPX4I", + "j/7yxZNTeDj/8qvZo+zRk0ezJ4+efPnFV+njJw9nT7786l/vGT5kQLaA+trdTyf/mZzlC5GcvTlPLg2w", + "DU5oyb4HszeoK88FNq4zSE3xJEJBWT556n/6P/6EHaeiaIb3v05cA5bJUutSPT05ubm5OQ4/OVlgUnii", + "RZUuT/w82E6sJa+8Oa+jyW3cC+5oYz3GTXWkcIbP3n5zcUnO3pwfNwQzeTo5PT49fuh613JassnTyWP8", + "CU/PEvf9xBHb5OmHj9PJyRJojjVUzB8FaMlS/0gCzTbu/+qGLhYgjzFhwP60enTixYqTDy45/uO2Zydh", + "SMXJh1YNgWzHlz5kYNcrJx98k8vtA7YaHLpgLYOdqK/wO9Cuoo41IkTKMaDLwI0+JUpIl3ZcSibMwZua", + "WzQDdKhjXJjEGtFaVjy1XlY7BXD876uz/0RP86uz/yRfk9Opi3FXqJnEprdJtTXFnGcW7H6An3q2OasL", + "VjRe6cnTdzFrkWtmVVaznKXEChx44gw5BQeiHrFheGganDTdxhv2bVjyafLV+w9f/OVjTCzsCbk1koIa", + "DiHqtfA9ChFpBV1/PYSytQt6NuP+owK5aRZR0PUkBLjvCo0UtvI5Kb5VaxjUF4T7/fvF6x+IkMSpwW9o", + "el3n4/gErCbpLMy/Ml8OQexuyBBo4FVhLhuX2FOoRdmu8Vqj+T32NUNAkS88Oj31zNCpGsEBPXHnPpip", + "Y5/qExrGtwQWx362syKwpqnON4SqIMAAw/18D8JO1pQok1bs9lYbZ39GtyXRwPd9E64jRciFpvkO+C47", + "/dpa6HCxMqW5LXdnOPeQEYXgfUweCLfW08ifu/vfY3f74gUphTnTDAOamyvHX2ctIJ1QmW88uAO1JI7J", + "30SFQqAR7ysNsW7VOIN1brg5XembIAKtyVbBJ0dH3YUfHTXxcnO4QSZLOb7YRcfR0bHZqSd7srKtBudW", + "pdhRZ2ef4Xqb9Yqu63BjSrjgCYcF1WwFJNAcn5w+/MOu8JzbAG8j9Vrp/ON08sUfeMvOuRFsaE7wTbua", + "x3/Y1VyAXLEUyCUUpZBUsnxDfuR1BH3QDLnP/n7k11zccI8Io3hWRUHlxgnRtOY5FQ9au2zlP70iNo2g", + "jVyULhQGtaCIamVaX+iOLybvP3odYKTuse21kxn2qhv7KoQKy7B2gi4GdfIBjeSDv584T2f8ITorrBZ8", + "4svrDbxpCynFH7a0og96bRayfTjzTjBeSnW6rMqTD/gfVGiDFdm67Cd6zU8wmPLkQwsR7nEPEe3fm8/D", + "N1aFyMADJ+Zz2+x/2+OTD/bfYCJYlyCZuY6wFqL71dasPcGer5v+zxueRn/sr6NVr3Pg5xNvT4mp1O03", + "P7T+bNOUWlY6EzfBLOiJsG60PmTmYaW6f5/cUKaNkOTKRGKT+P7HGmh+4nrCdH5tyrD3nmBt+eDHjlhV", + "Clsnpq3RvqU3l630Q2nrMzwTaKgYYrjrZMY4cqGQSzb2RfuwryL1eOPlEmwgrXfRRmRQLchMCpqlVGHv", + "cdc9qacbf7yj/tUtJ3EeccAhmGhu6FccNPzkeKdXBscdI2QG+0LOX/gJm8yt31ww60H0jGbEFxZKyCua", + "mw2HjJw58b+Fjd9aqPr8UtBnFls+mZzxzB8+RShWWWspiDJepyVoczZGqDBapGEAC+CJY0HJTGQb14lq", + "IumNXtuyEF3mdkLbN0bbEEklLdTQwwNYKX/fpsldFsk/DYF/GgL/NBX9aQj8c3f/NASONAT+aSb700z2", + "P9JMto9tLCZmOvPPsLSJrbFpa16r99GmBUHN4tsFq5iuZbJWPih2O2D6mJBLrJlCzS0BK5A0JylVVrpy", + "hbkKDNPEsleQPb3iSQsSGwxpJr7f/NdGoV5Vp6ePgZw+6H6jNMvzkDf3v0V5Fx/ZRJGvydXkatIbSUIh", + "VpDZrNawBLb9auew/6se93Wvdj6mj2NRGl8di6hqPmcpsyjPBV8QuhBNBDXWAOUCn4A0wNkORITpqcs4", + "Ya6cqGtQ3q7U3Zbc+xLAebOFO0MKOuQSjyYwhLdnKMG/jIkj+B8tpd+2DNRdGenWsXtc9U+u8im4ymfn", + "K390J21gWvxvKWY+OX3yh11QaIj+QWjyLWYH3E0cc7Up02gjptsKWr7Cijf3NRHGYcQu3qJ1rO679+Yi", + "UCBX/oJtAlCfnpxgya2lUPpkYq6/dnBq+PB9DfMHfzuVkq2w0y9aN4VkC8ZpnrjAz6QJMn10fDr5+P8D", + "AAD//23D8FrbHAEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 995363a491..3d581dbd78 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -26,6 +26,7 @@ import ( "math" "net/http" "os" + "strconv" "strings" "time" @@ -69,6 +70,19 @@ const MaxTealSourceBytes = 200_000 // become quite large, so we allow up to 1MB const MaxTealDryrunBytes = 1_000_000 +// MaxAssetResults sets a size limit for the number of assets returned in a single request to the +// /v2/accounts/{address}/assets endpoint +const MaxAssetResults = 1000 + +// DefaultAssetResults sets a default size limit for the number of assets returned in a single request to the +// /v2/accounts/{address}/assets endpoint +const DefaultAssetResults = uint64(1000) + +const ( + errInvalidLimit = "limit parameter must be a positive integer" + errUnableToParseNext = "unable to parse next token" +) + // WaitForBlockTimeout is the timeout for the WaitForBlock endpoint. var WaitForBlockTimeout = 1 * time.Minute @@ -91,6 +105,7 @@ type LedgerForAPI interface { ConsensusParams(r basics.Round) (config.ConsensusParams, error) Latest() basics.Round LookupAsset(rnd basics.Round, addr basics.Address, aidx basics.AssetIndex) (ledgercore.AssetResource, error) + LookupAssets(addr basics.Address, assetIDGT basics.AssetIndex, limit uint64) ([]ledgercore.AssetResourceWithIDs, basics.Round, error) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreement.Certificate, err error) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) @@ -1096,6 +1111,96 @@ func (v2 *Handlers) RawTransactionAsync(ctx echo.Context) error { return ctx.NoContent(http.StatusOK) } +// AccountAssetsInformation looks up an account's asset holdings. +// (GET /v2/accounts/{address}/assets) +func (v2 *Handlers) AccountAssetsInformation(ctx echo.Context, address string, params model.AccountAssetsInformationParams) error { + if !v2.Node.Config().EnableExperimentalAPI { + return ctx.String(http.StatusNotFound, "/v2/accounts/{address}/assets was not enabled in the configuration file by setting the EnableExperimentalAPI to true") + } + + addr, err := basics.UnmarshalChecksumAddress(address) + if err != nil { + return badRequest(ctx, err, errFailedToParseAddress, v2.Log) + } + + var assetGreaterThan uint64 = 0 + if params.Next != nil { + agt, err0 := strconv.ParseUint(*params.Next, 10, 64) + if err0 != nil { + return badRequest(ctx, err0, fmt.Sprintf("%s: %v", errUnableToParseNext, err0), v2.Log) + } + assetGreaterThan = agt + } + + if params.Limit != nil { + if *params.Limit <= 0 { + return badRequest(ctx, errors.New(errInvalidLimit), errInvalidLimit, v2.Log) + } + + if *params.Limit > MaxAssetResults { + limitErrMsg := fmt.Sprintf("limit %d exceeds max assets single batch limit %d", *params.Limit, MaxAssetResults) + return badRequest(ctx, errors.New(limitErrMsg), limitErrMsg, v2.Log) + } + } else { + // default limit + l := DefaultAssetResults + params.Limit = &l + } + + ledger := v2.Node.LedgerForAPI() + + // Logic + // 1. Get the account's asset holdings subject to limits + // 2. Handle empty response + // 3. Prepare JSON response + + // We intentionally request one more than the limit to determine if there are more assets. + records, lookupRound, err := ledger.LookupAssets(addr, basics.AssetIndex(assetGreaterThan), *params.Limit+1) + + if err != nil { + return internalError(ctx, err, errFailedLookingUpLedger, v2.Log) + } + + // prepare JSON response + response := model.AccountAssetsInformationResponse{Round: uint64(lookupRound)} + + // If the total count is greater than the limit, we set the next token to the last asset ID being returned + if uint64(len(records)) > *params.Limit { + // we do not include the last record in the response + records = records[:*params.Limit] + nextTk := strconv.FormatUint(uint64(records[len(records)-1].AssetID), 10) + response.NextToken = &nextTk + } + + assetHoldings := make([]model.AccountAssetHolding, 0, len(records)) + + for _, record := range records { + if record.AssetHolding == nil { + v2.Log.Warnf("AccountAssetsInformation: asset %d has no holding - should not be possible", record.AssetID) + continue + } + + aah := model.AccountAssetHolding{ + AssetHolding: model.AssetHolding{ + Amount: record.AssetHolding.Amount, + AssetID: uint64(record.AssetID), + IsFrozen: record.AssetHolding.Frozen, + }, + } + + if !record.Creator.IsZero() { + asset := AssetParamsToAsset(record.Creator.String(), record.AssetID, record.AssetParams) + aah.AssetParams = &asset.Params + } + + assetHoldings = append(assetHoldings, aah) + } + + response.AssetHoldings = &assetHoldings + + return ctx.JSON(http.StatusOK, response) +} + // PreEncodedSimulateTxnResult mirrors model.SimulateTransactionResult type PreEncodedSimulateTxnResult struct { Txn PreEncodedTxInfo `codec:"txn-result"` diff --git a/daemon/algod/api/server/v2/test/handlers_resources_test.go b/daemon/algod/api/server/v2/test/handlers_resources_test.go index 8260e54ffb..f028a9b2a1 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -21,6 +21,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "testing" "github.com/algorand/go-algorand/data/transactions/logic" @@ -107,6 +108,33 @@ func (l *mockLedger) LookupAsset(rnd basics.Round, addr basics.Address, aidx bas } return ar, nil } + +func (l *mockLedger) LookupAssets(addr basics.Address, assetIDGT basics.AssetIndex, limit uint64) ([]ledgercore.AssetResourceWithIDs, basics.Round, error) { + ad, ok := l.accounts[addr] + if !ok { + return nil, basics.Round(0), nil + } + + var res []ledgercore.AssetResourceWithIDs + for i := assetIDGT + 1; i < assetIDGT+1+basics.AssetIndex(limit); i++ { + apr := ledgercore.AssetResourceWithIDs{} + if ap, ok := ad.AssetParams[i]; ok { + apr.AssetParams = &ap + apr.Creator = basics.Address{} + } + + if ah, ok := ad.Assets[i]; ok { + apr.AssetHolding = &ah + } + + if apr.AssetParams != nil || apr.AssetHolding != nil { + apr.AssetID = i + res = append(res, apr) + } + } + return res, basics.Round(0), nil +} + func (l *mockLedger) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ar ledgercore.AppResource, err error) { ad, ok := l.accounts[addr] if !ok { @@ -211,6 +239,23 @@ func randomAccountWithAssetParams(N int) basics.AccountData { return a } +func randomAccountWithSomeAssetHoldingsAndOverlappingAssetParams(overlapN int, nonOverlapAssetHoldingsN int) basics.AccountData { + a := ledgertesting.RandomAccountData(0) + a.AssetParams = make(map[basics.AssetIndex]basics.AssetParams) + a.Assets = make(map[basics.AssetIndex]basics.AssetHolding) + // overlapN assets have both asset params and asset holdings + for i := 1; i <= overlapN; i++ { + a.AssetParams[basics.AssetIndex(i)] = ledgertesting.RandomAssetParams() + a.Assets[basics.AssetIndex(i)] = ledgertesting.RandomAssetHolding(false) + } + + // nonOverlapAssetHoldingsN assets have only asset holdings + for i := overlapN + 1; i <= (overlapN + nonOverlapAssetHoldingsN); i++ { + a.Assets[basics.AssetIndex(i)] = ledgertesting.RandomAssetHolding(false) + } + return a +} + func randomAccountWithAppLocalState(N int) basics.AccountData { a := ledgertesting.RandomAccountData(0) a.AppLocalStates = make(map[basics.AppIndex]basics.AppLocalState) @@ -241,6 +286,7 @@ func setupTestForLargeResources(t *testing.T, acctSize, maxResults int, accountM mockNode := makeMockNode(&ml, t.Name(), nil, cannedStatusReportGolden, false) mockNode.config.MaxAPIResourcesPerAccount = uint64(maxResults) + mockNode.config.EnableExperimentalAPI = true dummyShutdownChan := make(chan struct{}) handlers = v2.Handlers{ Node: mockNode, @@ -380,6 +426,120 @@ func accountInformationResourceLimitsTest(t *testing.T, accountMaker func(int) b } } +func accountAssetInformationResourceLimitsTest(t *testing.T, handlers v2.Handlers, addr basics.Address, + acctData basics.AccountData, params model.AccountAssetsInformationParams, inputNextToken int, maxResults int, expectToken bool) { + + ctx, rec := newReq(t) + err := handlers.AccountAssetsInformation(ctx, addr.String(), params) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + var ret model.AccountAssetsInformationResponse + err = json.Unmarshal(rec.Body.Bytes(), &ret) + require.NoError(t, err) + + if expectToken { + nextRaw, err0 := strconv.ParseUint(*ret.NextToken, 10, 64) + require.NoError(t, err0) + // The next token decoded is actually the last asset id returned + assert.Equal(t, (*ret.AssetHoldings)[maxResults-1].AssetHolding.AssetID, nextRaw) + } + assert.Equal(t, maxResults, len(*ret.AssetHoldings)) + + // Asset holdings should match the first limit assets from the account data + minForResults := 0 + if inputNextToken > 0 { + minForResults = inputNextToken + } + for i := minForResults; i < minForResults+maxResults; i++ { + expectedIndex := i + 1 + + assert.Equal(t, acctData.Assets[basics.AssetIndex(expectedIndex)].Amount, (*ret.AssetHoldings)[i-minForResults].AssetHolding.Amount) + assert.Equal(t, acctData.Assets[basics.AssetIndex(expectedIndex)].Frozen, (*ret.AssetHoldings)[i-minForResults].AssetHolding.IsFrozen) + assert.Equal(t, uint64(expectedIndex), (*ret.AssetHoldings)[i-minForResults].AssetHolding.AssetID) + } +} + +// TestAccountAssetsInformation tests the account asset information endpoint +func TestAccountAssetsInformation(t *testing.T) { + partitiontest.PartitionTest(t) + + accountOverlappingAssetParamsHoldingsCount := 1000 + accountNonOverlappingAssetHoldingsCount := 25 + totalAssetHoldings := accountOverlappingAssetParamsHoldingsCount + accountNonOverlappingAssetHoldingsCount + + handlers, addr, acctData := setupTestForLargeResources(t, accountOverlappingAssetParamsHoldingsCount, 50, func(N int) basics.AccountData { + return randomAccountWithSomeAssetHoldingsAndOverlappingAssetParams(N, accountNonOverlappingAssetHoldingsCount) + }) + + // 1. Query with no limit/pagination - should get DefaultAssetResults back + accountAssetInformationResourceLimitsTest(t, handlers, addr, acctData, model.AccountAssetsInformationParams{}, + 0, int(v2.DefaultAssetResults), false) + + rawLimit := 100 + limit := uint64(rawLimit) + // 2. Query with limit= totalAssetHoldings { + expectToken = false + expectedResultsCount = totalAssetHoldings - rawNext + } + accountAssetInformationResourceLimitsTest(t, handlers, addr, acctData, + model.AccountAssetsInformationParams{Limit: &limit, Next: &nextTk}, rawNext, expectedResultsCount, expectToken) + } + + // 4. Query with limit, next to provide batch, but no data in that range + rawNext := 1025 + nextTk := strconv.FormatUint(uint64(rawNext), 10) + accountAssetInformationResourceLimitsTest(t, handlers, addr, acctData, + model.AccountAssetsInformationParams{Limit: &limit, Next: &nextTk}, rawNext, totalAssetHoldings-rawNext, false) + + // 5. Malformed address + ctx, rec := newReq(t) + err := handlers.AccountAssetsInformation(ctx, "", model.AccountAssetsInformationParams{}) + require.NoError(t, err) + require.Equal(t, 400, rec.Code) + require.Equal(t, "{\"message\":\"failed to parse the address\"}\n", rec.Body.String()) + + // 6. Unknown address (200 returned, just no asset data) + unknownAddress := basics.Address{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + accountAssetInformationResourceLimitsTest(t, handlers, unknownAddress, basics.AccountData{}, model.AccountAssetsInformationParams{}, + 0, 0, false) + + // 7a. Invalid limits - larger than configured max + ctx, rec = newReq(t) + err = handlers.AccountAssetsInformation(ctx, addr.String(), model.AccountAssetsInformationParams{ + Limit: func() *uint64 { + l := uint64(v2.MaxAssetResults + 1) + return &l + }(), + }) + require.NoError(t, err) + require.Equal(t, 400, rec.Code) + require.Equal(t, "{\"message\":\"limit 1001 exceeds max assets single batch limit 1000\"}\n", rec.Body.String()) + + // 7b. Invalid limits - zero + ctx, rec = newReq(t) + err = handlers.AccountAssetsInformation(ctx, addr.String(), model.AccountAssetsInformationParams{ + Limit: func() *uint64 { + l := uint64(0) + return &l + }(), + }) + require.NoError(t, err) + require.Equal(t, 400, rec.Code) + require.Equal(t, "{\"message\":\"limit parameter must be a positive integer\"}\n", rec.Body.String()) + +} + func TestAccountInformationResourceLimits(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index e9c9696711..3f12955666 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -341,6 +341,10 @@ func (au *accountUpdates) LookupResource(rnd basics.Round, addr basics.Address, return au.lookupResource(rnd, addr, aidx, ctype, true /* take lock */) } +func (au *accountUpdates) LookupAssetResources(addr basics.Address, assetIDGT basics.AssetIndex, limit uint64) ([]ledgercore.AssetResourceWithIDs, basics.Round, error) { + return au.lookupAssetResources(addr, assetIDGT, limit) +} + func (au *accountUpdates) LookupKv(rnd basics.Round, key string) ([]byte, error) { return au.lookupKv(rnd, key, true /* take lock */) } @@ -1208,6 +1212,55 @@ func (au *accountUpdates) lookupResource(rnd basics.Round, addr basics.Address, } } +// lookupAllResources returns all the resources for a given address, solely based on what is persisted to disk. It does not +// take into account any in-memory deltas; the round number returned is the latest round number that is known to the database. +func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT basics.AssetIndex, limit uint64) (data []ledgercore.AssetResourceWithIDs, validThrough basics.Round, err error) { + // Look for resources on disk + persistedResources, resourceDbRound, err0 := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(assetIDGT), limit, basics.AssetCreatable) + if err0 != nil { + return nil, basics.Round(0), err0 + } + + data = make([]ledgercore.AssetResourceWithIDs, 0, len(persistedResources)) + for _, pd := range persistedResources { + ah := pd.Data.GetAssetHolding() + + var arwi ledgercore.AssetResourceWithIDs + if !pd.Creator.IsZero() { + ap := pd.Data.GetAssetParams() + + arwi = ledgercore.AssetResourceWithIDs{ + AssetID: basics.AssetIndex(pd.Aidx), + Creator: pd.Creator, + + AssetResource: ledgercore.AssetResource{ + AssetHolding: &ah, + AssetParams: &ap, + }, + } + } else { + arwi = ledgercore.AssetResourceWithIDs{ + AssetID: basics.AssetIndex(pd.Aidx), + + AssetResource: ledgercore.AssetResource{ + AssetHolding: &ah, + }, + } + } + + data = append(data, arwi) + } + // We've found all the resources we could find for this address. + currentDbRound := resourceDbRound + // The resourceDbRound will not be set if there are no persisted resources + if len(data) == 0 { + au.accountsMu.RLock() + currentDbRound = au.cachedDBRound + au.accountsMu.RUnlock() + } + return data, currentDbRound, nil +} + func (au *accountUpdates) lookupStateDelta(rnd basics.Round) (ledgercore.StateDelta, error) { au.accountsMu.RLock() defer au.accountsMu.RUnlock() diff --git a/ledger/catchpointfilewriter_test.go b/ledger/catchpointfilewriter_test.go index 71a0737fb6..2f53cc59b6 100644 --- a/ledger/catchpointfilewriter_test.go +++ b/ledger/catchpointfilewriter_test.go @@ -685,7 +685,9 @@ func testNewLedgerFromCatchpoint(t *testing.T, catchpointWriterReadAccess tracke var initState ledgercore.InitState initState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion conf := config.GetDefaultLocal() - l, err := OpenLedger(logging.TestingLog(t), t.Name()+"FromCatchpoint", true, initState, conf) + dbName := fmt.Sprintf("%s.%d", t.Name()+"FromCatchpoint", crypto.RandUint64()) + dbName = strings.Replace(dbName, "/", "_", -1) + l, err := OpenLedger(logging.TestingLog(t), dbName, true, initState, conf) require.NoError(t, err) accessor := MakeCatchpointCatchupAccessor(l, l.log) @@ -702,6 +704,8 @@ func testNewLedgerFromCatchpoint(t *testing.T, catchpointWriterReadAccess tracke err = accessor.BuildMerkleTrie(context.Background(), nil) require.NoError(t, err) + resetAccountDBToV6(t, l) + err = l.trackerDBs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) error { cw, err := tx.MakeCatchpointWriter() if err != nil { diff --git a/ledger/ledger.go b/ledger/ledger.go index 60cad748f7..2cc1b36ee1 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -583,6 +583,12 @@ func (l *Ledger) LookupAsset(rnd basics.Round, addr basics.Address, aidx basics. return ledgercore.AssetResource{AssetParams: r.AssetParams, AssetHolding: r.AssetHolding}, err } +// LookupAssets loads asset resources that match the request parameters from the ledger. +func (l *Ledger) LookupAssets(addr basics.Address, assetIDGT basics.AssetIndex, limit uint64) ([]ledgercore.AssetResourceWithIDs, basics.Round, error) { + resources, lookupRound, err := l.accts.LookupAssetResources(addr, assetIDGT, limit) + return resources, lookupRound, err +} + // lookupResource loads a resource that matches the request parameters from the accounts update func (l *Ledger) lookupResource(rnd basics.Round, addr basics.Address, aidx basics.CreatableIndex, ctype basics.CreatableType) (ledgercore.AccountResource, error) { l.trackerMu.RLock() diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 0e3b066b80..127ecf85cb 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -53,6 +53,8 @@ import ( "github.com/algorand/go-deadlock" ) +const preReleaseDBVersion = 6 + func sign(secrets map[basics.Address]*crypto.SignatureSecrets, t transactions.Transaction) transactions.SignedTxn { var sig crypto.Signature _, ok := secrets[t.Sender] @@ -2178,6 +2180,42 @@ func TestLedgerReloadShrinkDeltas(t *testing.T) { } } +func resetAccountDBToV6(t *testing.T, l *Ledger) { + // reset tables and re-init again, similary to the catchpount apply code + // since the ledger has only genesis accounts, this recreates them + err := l.trackerDBs.Transaction(func(ctx context.Context, tx trackerdb.TransactionScope) error { + arw, err := tx.MakeAccountsWriter() + if err != nil { + return err + } + + err0 := arw.AccountsReset(ctx) + if err0 != nil { + return err0 + } + tp := trackerdb.Params{ + InitAccounts: l.GenesisAccounts(), + InitProto: l.GenesisProtoVersion(), + GenesisHash: l.GenesisHash(), + FromCatchpoint: true, + CatchpointEnabled: l.catchpoint.catchpointEnabled(), + DbPathPrefix: l.catchpoint.dbDirectory, + BlockDb: l.blockDBs, + } + _, err0 = tx.RunMigrations(ctx, tp, l.log, preReleaseDBVersion /*target database version*/) + if err0 != nil { + return err0 + } + + if err0 := tx.Testing().AccountsUpdateSchemaTest(ctx); err != nil { + return err0 + } + + return nil + }) + require.NoError(t, err) +} + // TestLedgerReloadTxTailHistoryAccess checks txtail has MaxTxnLife + DeeperBlockHeaderHistory block headers // for TEAL after applying catchpoint. // Simulate catchpoints by the following: @@ -2189,8 +2227,6 @@ func TestLedgerReloadShrinkDeltas(t *testing.T) { func TestLedgerReloadTxTailHistoryAccess(t *testing.T) { partitiontest.PartitionTest(t) - const preReleaseDBVersion = 6 - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 10_000_000_000) proto := config.Consensus[protocol.ConsensusCurrentVersion] diff --git a/ledger/ledgercore/accountresource.go b/ledger/ledgercore/accountresource.go index e1b13e71d7..e8f03bd60e 100644 --- a/ledger/ledgercore/accountresource.go +++ b/ledger/ledgercore/accountresource.go @@ -34,6 +34,14 @@ type AssetResource struct { AssetHolding *basics.AssetHolding } +// AssetResourceWithIDs is used to retrieve a asset resource information from the data tier, +// inclusive of the asset ID and creator address +type AssetResourceWithIDs struct { + AssetResource + AssetID basics.AssetIndex + Creator basics.Address +} + // AppResource used to retrieve a generic app resource information from the data tier type AppResource struct { AppLocalState *basics.AppLocalState diff --git a/ledger/store/trackerdb/data.go b/ledger/store/trackerdb/data.go index 74e67e70b1..8e69f2fc69 100644 --- a/ledger/store/trackerdb/data.go +++ b/ledger/store/trackerdb/data.go @@ -206,6 +206,14 @@ type PersistedResourcesData struct { Round basics.Round } +// PersistedResourcesDataWithCreator is exported view of persistedResourcesData inclusive of creator +type PersistedResourcesDataWithCreator struct { + PersistedResourcesData + + // the address of the account that created this resource + Creator basics.Address +} + // PersistedOnlineAccountData is exported view of persistedOnlineAccountData type PersistedOnlineAccountData struct { Addr basics.Address diff --git a/ledger/store/trackerdb/dualdriver/accounts_reader.go b/ledger/store/trackerdb/dualdriver/accounts_reader.go index b39aadd7cb..5692994636 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_reader.go +++ b/ledger/store/trackerdb/dualdriver/accounts_reader.go @@ -185,3 +185,39 @@ func (ar *accountsReader) LookupResources(addr basics.Address, aidx basics.Creat // return primary results return dataP, nil } + +func (ar *accountsReader) LookupLimitedResources(addr basics.Address, minIdx basics.CreatableIndex, maxCreatables uint64, ctype basics.CreatableType) (data []trackerdb.PersistedResourcesDataWithCreator, rnd basics.Round, err error) { + dataP, rndP, errP := ar.primary.LookupLimitedResources(addr, minIdx, maxCreatables, ctype) + dataS, rndS, errS := ar.secondary.LookupLimitedResources(addr, minIdx, maxCreatables, ctype) + // coalesce errors + err = coalesceErrors(errP, errS) + if err != nil { + return + } + // coalesce refs + if len(dataP) != len(dataS) { + err = ErrInconsistentResult + return + } + var ref trackerdb.AccountRef + for i := range dataP { + ref, err = coalesceAccountRefs(dataP[i].AcctRef, dataS[i].AcctRef) + if err != nil { + return data, rnd, err + } + // update ref in results + dataP[i].AcctRef = ref + dataS[i].AcctRef = ref + } + // check results match + if !cmp.Equal(dataP, dataS, allowAllUnexported) { + err = ErrInconsistentResult + return + } + if rndP != rndS { + err = ErrInconsistentResult + return + } + // return primary results + return dataP, rndP, nil +} diff --git a/ledger/store/trackerdb/generickv/accounts_reader.go b/ledger/store/trackerdb/generickv/accounts_reader.go index 063b5a35f5..0aa9d55a21 100644 --- a/ledger/store/trackerdb/generickv/accounts_reader.go +++ b/ledger/store/trackerdb/generickv/accounts_reader.go @@ -17,6 +17,7 @@ package generickv import ( + "errors" "fmt" "io" @@ -178,6 +179,10 @@ func (r *accountsReader) LookupAllResources(addr basics.Address) (data []tracker return } +func (r *accountsReader) LookupLimitedResources(_ basics.Address, _ basics.CreatableIndex, _ uint64, _ basics.CreatableType) ([]trackerdb.PersistedResourcesDataWithCreator, basics.Round, error) { + return nil, 0, errors.New("not supported") +} + func (r *accountsReader) LookupKeyValue(key string) (pv trackerdb.PersistedKVData, err error) { // read the current db round pv.Round, err = r.AccountsRound() diff --git a/ledger/store/trackerdb/generickv/migrations.go b/ledger/store/trackerdb/generickv/migrations.go index c2a0d0f208..a329d84bf7 100644 --- a/ledger/store/trackerdb/generickv/migrations.go +++ b/ledger/store/trackerdb/generickv/migrations.go @@ -222,6 +222,6 @@ func (m *migrator) initialVersion(ctx context.Context) error { return err } - // KV store starts at version 10 - return m.setVersion(ctx, 10) + // KV store starts at version 11 + return m.setVersion(ctx, 11) } diff --git a/ledger/store/trackerdb/interface.go b/ledger/store/trackerdb/interface.go index df9de98d17..25d7b79e3c 100644 --- a/ledger/store/trackerdb/interface.go +++ b/ledger/store/trackerdb/interface.go @@ -105,6 +105,7 @@ type AccountsReader interface { LookupResources(addr basics.Address, aidx basics.CreatableIndex, ctype basics.CreatableType) (data PersistedResourcesData, err error) LookupAllResources(addr basics.Address) (data []PersistedResourcesData, rnd basics.Round, err error) + LookupLimitedResources(addr basics.Address, minIdx basics.CreatableIndex, maxCreatables uint64, ctype basics.CreatableType) (data []PersistedResourcesDataWithCreator, rnd basics.Round, err error) LookupKeyValue(key string) (pv PersistedKVData, err error) LookupKeysByPrefix(prefix string, maxKeyNum uint64, results map[string]bool, resultCount uint64) (round basics.Round, err error) diff --git a/ledger/store/trackerdb/sqlitedriver/schema.go b/ledger/store/trackerdb/sqlitedriver/schema.go index a36dd2cd90..d938ba180f 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema.go +++ b/ledger/store/trackerdb/sqlitedriver/schema.go @@ -21,6 +21,7 @@ import ( "context" "database/sql" "encoding/hex" + "errors" "fmt" "time" @@ -941,3 +942,98 @@ func convertOnlineRoundParamsTail(ctx context.Context, e db.Executable) error { _, err := e.ExecContext(ctx, createVoteLastValidIndex) return err } + +func accountsAddCreatableTypeColumn(ctx context.Context, e db.Executable, populateColumn bool) error { + // Run ctype resources migration if it hasn't run yet + var creatableTypeOnResourcesRun bool + err := e.QueryRow("SELECT 1 FROM pragma_table_info('resources') WHERE name='ctype'").Scan(&creatableTypeOnResourcesRun) + if err == nil { + // Check if any ctypes are invalid + var count uint64 + err0 := e.QueryRow("SELECT COUNT(*) FROM resources WHERE ctype NOT IN (0, 1)").Scan(&count) + if err0 != nil { + return err0 + } + if count > 0 { + // Invalid ctypes found, return an error + return fmt.Errorf("invalid ctypes found in resources table; database is corrupted and needs to be rebuilt") + } + // Column exists, no ctypes are invalid, no migration needed so return clean + return nil + } else if !errors.Is(err, sql.ErrNoRows) { + return err + } // A sql.ErrNoRows error means the column does not exist, so we need to create it/run the migration + + // If we reached here, a sql.ErrNoRows error was returned, so we need to create the column + + // Add ctype column + createStmt := `ALTER TABLE resources ADD COLUMN ctype INTEGER NOT NULL DEFAULT -1` + + _, err = e.ExecContext(ctx, createStmt) + if err != nil { + return err + } + + if populateColumn { + // Populate the new ctype column with the corresponding creatable type from assetcreators where available + updateStmt := `UPDATE resources SET ctype = ( + SELECT COALESCE((SELECT ac.ctype FROM assetcreators ac WHERE ac.asset = resources.aidx),-1) + ) WHERE ctype = -1` + + _, err0 := e.ExecContext(ctx, updateStmt) + if err0 != nil { + return err0 + } + + updatePrepStmt, err0 := e.PrepareContext(ctx, "UPDATE resources SET ctype = ? WHERE addrid = ? AND aidx = ?") + if err0 != nil { + return err0 + } + defer updatePrepStmt.Close() + + // Pull resource entries into memory where ctype is not set + rows, err0 := e.QueryContext(ctx, "SELECT addrid, aidx, data FROM resources r WHERE ctype = -1") + if err0 != nil { + return err0 + } + defer rows.Close() + + // Update the ctype column for subset of resources where ctype was not resolved from assetcreators + for rows.Next() { + var addrid int64 + var aidx int64 + var encodedData []byte + err0 = rows.Scan(&addrid, &aidx, &encodedData) + if err0 != nil { + return err0 + } + + var rd trackerdb.ResourcesData + err0 = protocol.Decode(encodedData, &rd) + if err0 != nil { + return err0 + } + + var ct basics.CreatableType + if rd.IsAsset() && rd.IsApp() { + // This should never happen! + return fmt.Errorf("unable to discern creatable type for addrid %d, resource %d", addrid, aidx) + } else if rd.IsAsset() { + ct = basics.AssetCreatable + } else if rd.IsApp() { + ct = basics.AppCreatable + } else { // This should never happen! + return fmt.Errorf("unable to discern creatable type for addrid %d, resource %d", addrid, aidx) + } + + _, err0 = updatePrepStmt.ExecContext(ctx, ct, addrid, aidx) + if err0 != nil { + return err0 + } + } + + return rows.Err() + } + + return nil +} diff --git a/ledger/store/trackerdb/sqlitedriver/sql.go b/ledger/store/trackerdb/sqlitedriver/sql.go index 634c7f54ef..1befd06298 100644 --- a/ledger/store/trackerdb/sqlitedriver/sql.go +++ b/ledger/store/trackerdb/sqlitedriver/sql.go @@ -19,7 +19,6 @@ package sqlitedriver import ( "database/sql" "fmt" - "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" @@ -30,12 +29,13 @@ import ( // accountsDbQueries is used to cache a prepared SQL statement to look up // the state of a single account. type accountsDbQueries struct { - lookupAccountStmt *sql.Stmt - lookupResourcesStmt *sql.Stmt - lookupAllResourcesStmt *sql.Stmt - lookupKvPairStmt *sql.Stmt - lookupKeysByRangeStmt *sql.Stmt - lookupCreatorStmt *sql.Stmt + lookupAccountStmt *sql.Stmt + lookupResourcesStmt *sql.Stmt + lookupAllResourcesStmt *sql.Stmt + lookupLimitedResourcesStmt *sql.Stmt + lookupKvPairStmt *sql.Stmt + lookupKeysByRangeStmt *sql.Stmt + lookupCreatorStmt *sql.Stmt } type onlineAccountsDbQueries struct { @@ -87,6 +87,29 @@ func AccountsInitDbQueries(q db.Queryable) (*accountsDbQueries, error) { return nil, err } + qs.lookupLimitedResourcesStmt, err = q.Prepare( + `SELECT ab.rowid, + ar.rnd, + r.aidx, + ac.creator, + r.data, + cr.data + FROM acctrounds ar + JOIN accountbase ab ON ab.address = ? + JOIN resources r ON r.addrid = ab.addrid + LEFT JOIN assetcreators ac ON r.aidx = ac.asset + LEFT JOIN accountbase cab ON ac.creator = cab.address + LEFT JOIN resources cr ON cr.addrid = cab.addrid + AND cr.aidx = r.aidx + WHERE ar.id = 'acctbase' + AND r.ctype = ? + AND r.aidx > ? + ORDER BY r.aidx ASC + LIMIT ?`) + if err != nil { + return nil, err + } + qs.lookupKvPairStmt, err = q.Prepare("SELECT acctrounds.rnd, kvstore.key, kvstore.value FROM acctrounds LEFT JOIN kvstore ON key = ? WHERE id='acctbase';") if err != nil { return nil, err @@ -168,7 +191,7 @@ func MakeAccountsSQLWriter(e db.Executable, hasAccounts, hasResources, hasKvPair return } - w.insertResourceStmt, err = e.Prepare("INSERT INTO resources(addrid, aidx, data) VALUES(?, ?, ?)") + w.insertResourceStmt, err = e.Prepare("INSERT INTO resources(addrid, aidx, data, ctype) VALUES(?, ?, ?, ?)") if err != nil { return } @@ -379,9 +402,9 @@ func (qs *accountsDbQueries) LookupResources(addr basics.Address, aidx basics.Cr func (qs *accountsDbQueries) LookupAllResources(addr basics.Address) (data []trackerdb.PersistedResourcesData, rnd basics.Round, err error) { err = db.Retry(func() error { // Query for all resources - rows, err := qs.lookupAllResourcesStmt.Query(addr[:]) - if err != nil { - return err + rows, err0 := qs.lookupAllResourcesStmt.Query(addr[:]) + if err0 != nil { + return err0 } defer rows.Close() @@ -390,7 +413,7 @@ func (qs *accountsDbQueries) LookupAllResources(addr basics.Address) (data []tra data = nil var buf []byte for rows.Next() { - err := rows.Scan(&addrid, &dbRound, &aidx, &buf) + err = rows.Scan(&addrid, &dbRound, &aidx, &buf) if err != nil { return err } @@ -421,6 +444,89 @@ func (qs *accountsDbQueries) LookupAllResources(addr basics.Address) (data []tra return } +func (qs *accountsDbQueries) LookupLimitedResources(addr basics.Address, minIdx basics.CreatableIndex, maxCreatables uint64, ctype basics.CreatableType) (data []trackerdb.PersistedResourcesDataWithCreator, rnd basics.Round, err error) { + err = db.Retry(func() error { + rows, err0 := qs.lookupLimitedResourcesStmt.Query(addr[:], ctype, minIdx, maxCreatables) + if err0 != nil { + return err0 + } + defer rows.Close() + + var addrid, aidx sql.NullInt64 + var dbRound basics.Round + data = nil + var actAssetBuf []byte + var crtAssetBuf []byte + var creatorAddrBuf []byte + for rows.Next() { + err = rows.Scan(&addrid, &dbRound, &aidx, &creatorAddrBuf, &actAssetBuf, &crtAssetBuf) + if err != nil { + return err + } + if !addrid.Valid || !aidx.Valid { + // we received an entry without any index. This would happen only on the first entry when there are no resources for this address. + // ensure this is the first entry, set the round and return + if len(data) != 0 { + return fmt.Errorf("LookupLimitedResources: unexpected invalid result on non-first resource record: (%v, %v)", addrid.Valid, aidx.Valid) + } + rnd = dbRound + break + } + var actResData trackerdb.ResourcesData + var crtResData trackerdb.ResourcesData + err = protocol.Decode(actAssetBuf, &actResData) + if err != nil { + return err + } + + var prdwc trackerdb.PersistedResourcesDataWithCreator + if len(crtAssetBuf) > 0 { + err = protocol.Decode(crtAssetBuf, &crtResData) + if err != nil { + return err + } + + // Since there is a creator, we want to return all of the asset params along with the asset holdings. + // The most simple way to do this is to set the necessary asset holding data on the creator resource data + // retrieved from the database. Note that this is unique way of setting resource flags, making this structure + // not suitable for use in other contexts (where the params would only be present colocated with the asset holding + // of the creator). + crtResData.Amount = actResData.Amount + crtResData.Frozen = actResData.Frozen + crtResData.ResourceFlags = actResData.ResourceFlags + + creatorAddr := basics.Address{} + copy(creatorAddr[:], creatorAddrBuf) + + prdwc = trackerdb.PersistedResourcesDataWithCreator{ + PersistedResourcesData: trackerdb.PersistedResourcesData{ + AcctRef: sqlRowRef{addrid.Int64}, + Aidx: basics.CreatableIndex(aidx.Int64), + Data: crtResData, + Round: dbRound, + }, + Creator: creatorAddr, + } + } else { // no creator found, asset was likely deleted, will not have asset params + prdwc = trackerdb.PersistedResourcesDataWithCreator{ + PersistedResourcesData: trackerdb.PersistedResourcesData{ + AcctRef: sqlRowRef{addrid.Int64}, + Aidx: basics.CreatableIndex(aidx.Int64), + Data: actResData, + Round: dbRound, + }, + } + } + + data = append(data, prdwc) + + rnd = dbRound + } + return nil + }) + return +} + // LookupAccount looks up for a the account data given it's address. It returns the persistedAccountData, which includes the current database round and the matching // account data, if such was found. If no matching account data could be found for the given address, an empty account data would // be retrieved. @@ -541,6 +647,7 @@ func (qs *accountsDbQueries) Close() { &qs.lookupAccountStmt, &qs.lookupResourcesStmt, &qs.lookupAllResourcesStmt, + &qs.lookupLimitedResourcesStmt, &qs.lookupKvPairStmt, &qs.lookupKeysByRangeStmt, &qs.lookupCreatorStmt, @@ -633,7 +740,17 @@ func (w accountsSQLWriter) InsertResource(accountRef trackerdb.AccountRef, aidx return nil, fmt.Errorf("no account could be found for rowid = nil: %w", err) } addrid := accountRef.(sqlRowRef).rowid - result, err := w.insertResourceStmt.Exec(addrid, aidx, protocol.Encode(&data)) + var ctype basics.CreatableType + if data.IsAsset() && data.IsApp() { + return nil, fmt.Errorf("unable to resolve single creatable type for account ref %d, creatable idx %d", addrid, aidx) + } else if data.IsAsset() { + ctype = basics.AssetCreatable + } else if data.IsApp() { + ctype = basics.AppCreatable + } else { + return nil, fmt.Errorf("unable to resolve creatable type for account ref %d, creatable idx %d", addrid, aidx) + } + result, err := w.insertResourceStmt.Exec(addrid, aidx, protocol.Encode(&data), ctype) if err != nil { return } diff --git a/ledger/store/trackerdb/sqlitedriver/testing.go b/ledger/store/trackerdb/sqlitedriver/testing.go index 49736a681a..fd4076799b 100644 --- a/ledger/store/trackerdb/sqlitedriver/testing.go +++ b/ledger/store/trackerdb/sqlitedriver/testing.go @@ -101,26 +101,37 @@ func AccountsInitTest(tb testing.TB, e db.Executable, initAccounts map[basics.Ad err = performKVStoreNullBlobConversion(context.Background(), e) require.NoError(tb, err) + err = accountsAddCreatableTypeColumn(context.Background(), e, false) + require.NoError(tb, err) + return newDB } // AccountsUpdateSchemaTest adds some empty tables for tests to work with a "v6" store. func AccountsUpdateSchemaTest(ctx context.Context, e db.Executable) (err error) { - if err := accountsCreateOnlineAccountsTable(ctx, e); err != nil { + if err = accountsCreateOnlineAccountsTable(ctx, e); err != nil { return err } - if err := accountsCreateTxTailTable(ctx, e); err != nil { + if err = accountsCreateTxTailTable(ctx, e); err != nil { return err } - if err := accountsCreateOnlineRoundParamsTable(ctx, e); err != nil { + if err = accountsCreateOnlineRoundParamsTable(ctx, e); err != nil { return err } - if err := accountsCreateCatchpointFirstStageInfoTable(ctx, e); err != nil { + if err = accountsCreateCatchpointFirstStageInfoTable(ctx, e); err != nil { return err } // this line creates kvstore table, even if it is not required in accountDBVersion 6 -> 7 // or in later version where we need kvstore table, some tests will fail - if err := accountsCreateBoxTable(ctx, e); err != nil { + if err = accountsCreateBoxTable(ctx, e); err != nil { + return err + } + // this adds the resources table and ctype column, even if it is not required in accountDBVersion 6 -> 7 + // this prevents some tests from failing. + if err = accountsCreateResourceTable(ctx, e); err != nil { + return err + } + if err = accountsAddCreatableTypeColumn(ctx, e, false); err != nil { return err } return createStateProofVerificationTable(ctx, e) diff --git a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go index db1416b521..9987ef8b03 100644 --- a/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go +++ b/ledger/store/trackerdb/sqlitedriver/trackerdbV2.go @@ -136,6 +136,12 @@ func RunMigrations(ctx context.Context, e db.Executable, params trackerdb.Params tu.log.Warnf("trackerDBInitialize failed to upgrade accounts database (ledger.tracker.sqlite) from schema 9 : %v", err) return } + case 10: + err = tu.upgradeDatabaseSchema10(ctx, e) + if err != nil { + tu.log.Warnf("trackerDBInitialize failed to upgrade accounts database (ledger.tracker.sqlite) from schema 10 : %v", err) + return + } default: return trackerdb.InitParams{}, fmt.Errorf("trackerDBInitialize unable to upgrade database from schema version %d", tu.schemaVersion) } @@ -507,6 +513,17 @@ func (tu *trackerDBSchemaInitializer) upgradeDatabaseSchema9(ctx context.Context return tu.setVersion(ctx, e, 10) } +// upgradeDatabaseSchema10 upgrades the database schema from version 10 to version 11, +// altering the resources table to add a new column, resources.ctype. +func (tu *trackerDBSchemaInitializer) upgradeDatabaseSchema10(ctx context.Context, e db.Executable) (err error) { + err = accountsAddCreatableTypeColumn(ctx, e, true) + if err != nil { + return err + } + // update version + return tu.setVersion(ctx, e, 11) +} + func removeEmptyDirsOnSchemaUpgrade(dbDirectory string) (err error) { catchpointRootDir := filepath.Join(dbDirectory, trackerdb.CatchpointDirName) if _, err := os.Stat(catchpointRootDir); os.IsNotExist(err) { diff --git a/ledger/store/trackerdb/testsuite/accounts_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_kv_test.go index 115e958901..b804ec8cb9 100644 --- a/ledger/store/trackerdb/testsuite/accounts_kv_test.go +++ b/ledger/store/trackerdb/testsuite/accounts_kv_test.go @@ -19,6 +19,7 @@ package testsuite import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/store/trackerdb" + ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/stretchr/testify/require" ) @@ -27,6 +28,9 @@ func init() { registerTest("accounts-crud", CustomTestAccountsCrud) registerTest("resources-crud", CustomTestResourcesCrud) registerTest("resources-query-all", CustomTestResourcesQueryAll) + // NOTE: this test is disabled because it is not supported by the kv implementation, + // it is only supported by the sqlite implementation and is enabled there (see sqlitedb_test.go) + // registerTest("resources-query-all-limited", CustomTestResourcesQueryAllLimited) registerTest("kv-crud", CustomTestAppKVCrud) registerTest("creatables-crud", CustomTestCreatablesCrud) } @@ -210,12 +214,14 @@ func CustomTestResourcesQueryAll(t *customT) { // resource A-0 resDataA0 := trackerdb.ResourcesData{} + resDataA0.SetAssetHolding(basics.AssetHolding{Amount: 0}) aidxResA0 := basics.CreatableIndex(0) _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0) require.NoError(t, err) // resource A-1 resDataA1 := trackerdb.ResourcesData{} + resDataA1.SetAssetHolding(basics.AssetHolding{Amount: 0}) aidxResA1 := basics.CreatableIndex(1) _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1) require.NoError(t, err) @@ -232,6 +238,195 @@ func CustomTestResourcesQueryAll(t *customT) { require.Equal(t, expectedRound, rnd) // db round (from the return) } +func CustomTestResourcesQueryAllLimited(t *customT) { + aow, err := t.db.MakeAccountsOptimizedWriter(true, true, false, true) + require.NoError(t, err) + + aor, err := t.db.MakeAccountsOptimizedReader() + require.NoError(t, err) + + aw, err := t.db.MakeAccountsWriter() + require.NoError(t, err) + + // set round to 3 + // Note: this will be used to check that we read the round + expectedRound := basics.Round(3) + err = aw.UpdateAccountsRound(expectedRound) + require.NoError(t, err) + + // + // pre-fill the db with two accounts for testing - one owning creatables, the other opting into them + // + + // account A - will own creatables + addrA := RandomAddress() + accDataA := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccA, err := aow.InsertAccount(addrA, accDataA.NormalizedOnlineBalance(t.proto), accDataA) + require.NoError(t, err) + + // account B - will opt into creatables + addrB := RandomAddress() + accDataB := trackerdb.BaseAccountData{RewardsBase: 1000} + refAccB, err := aow.InsertAccount(addrB, accDataB.NormalizedOnlineBalance(t.proto), accDataB) + require.NoError(t, err) + + // asset A-0 for accounts A and B + resDataA0AcctA := trackerdb.ResourcesData{} + resDataA0AcctA.SetAssetHolding(basics.AssetHolding{Amount: 10}) + resDataA0AcctA.SetAssetParams(basics.AssetParams{ + Total: 100, + }, true) + // Non-creators will inherit asset params from the creator + resDataA0AcctB := trackerdb.ResourcesData{} + resDataA0AcctB.SetAssetHolding(basics.AssetHolding{Amount: 0}) + aidxResA0 := basics.CreatableIndex(1) + + _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0AcctA) + require.NoError(t, err) + _, err = aow.InsertResource(refAccB, aidxResA0, resDataA0AcctB) + require.NoError(t, err) + + // App A-1 for accounts A and B - this should be completely ignored + resDataA1AcctA := trackerdb.ResourcesData{} + appParams := ledgertesting.RandomAppParams() + resDataA1AcctA.SetAppParams(appParams, true) + resDataA1AcctA.SetAppLocalState(basics.AppLocalState{}) + resDataA1AcctB := trackerdb.ResourcesData{} + resDataA1AcctB.SetAppLocalState(basics.AppLocalState{}) + aidxResA1 := basics.CreatableIndex(2) + _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1AcctA) + require.NoError(t, err) + _, err = aow.InsertResource(refAccB, aidxResA1, resDataA1AcctB) + require.NoError(t, err) + + // asset A-2 for accounts A and B + resDataA2AcctA := trackerdb.ResourcesData{} + resDataA2AcctA.SetAssetHolding(basics.AssetHolding{Amount: 100}) + resDataA2AcctA.SetAssetParams(basics.AssetParams{ + Total: 10000, + }, true) + resDataA2AcctB := trackerdb.ResourcesData{} + resDataA2AcctB.SetAssetHolding(basics.AssetHolding{Amount: 200}) + + aidxResA2 := basics.CreatableIndex(3) + _, err = aow.InsertResource(refAccA, aidxResA2, resDataA2AcctA) + require.NoError(t, err) + _, err = aow.InsertResource(refAccB, aidxResA2, resDataA2AcctB) + require.NoError(t, err) + + // Results for account B (opted in, not creator) we expect back will have asset params but the resource flags + // are explicitly set to be + resDataWithParamsA0AcctB := trackerdb.ResourcesData{} + resDataWithParamsA0AcctB.SetAssetHolding(resDataA0AcctB.GetAssetHolding()) + resDataWithParamsA0AcctB.SetAssetParams(resDataA0AcctA.GetAssetParams(), true) + resDataWithParamsA0AcctB.ResourceFlags = resDataA0AcctB.ResourceFlags + + resDataWithParamsA1AcctB := trackerdb.ResourcesData{} + resDataWithParamsA1AcctB.SetAssetHolding(resDataA2AcctB.GetAssetHolding()) + resDataWithParamsA1AcctB.SetAssetParams(resDataA2AcctA.GetAssetParams(), true) + resDataWithParamsA1AcctB.ResourceFlags = resDataA2AcctB.ResourceFlags + + // insert creator account A for A-0 + resA0ctype := basics.AssetCreatable + cRefA0, err := aow.InsertCreatable(aidxResA0, resA0ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA0) + + // insert creator account A for A-1 + resA1ctype := basics.AppCreatable + cRefA1, err := aow.InsertCreatable(aidxResA1, resA1ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA1) + + // insert creator account A for A-2 + resA2ctype := basics.AssetCreatable + cRefA2, err := aow.InsertCreatable(aidxResA2, resA2ctype, addrA[:]) + require.NoError(t, err) + require.NotNil(t, cRefA2) + + // Lookup with limited resources for account A + prs, rnd, err := aor.LookupLimitedResources(addrA, 0, 2, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, 2, len(prs)) + require.Equal(t, aidxResA0, prs[0].Aidx) + require.Equal(t, aidxResA2, prs[1].Aidx) + require.Equal(t, addrA, prs[0].Creator) + require.Equal(t, addrA, prs[1].Creator) + require.Equal(t, expectedRound, prs[0].Round) // db round (inside resources) + require.Equal(t, expectedRound, prs[1].Round) + require.Equal(t, resDataA0AcctA, prs[0].Data) + require.Equal(t, resDataA2AcctA, prs[1].Data) + require.Equal(t, expectedRound, rnd) // db round (from the return) + + // Lookup with limited resources for account B + prs, rnd, err = aor.LookupLimitedResources(addrB, 0, 2, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, 2, len(prs)) + require.Equal(t, aidxResA0, prs[0].Aidx) + require.Equal(t, aidxResA2, prs[1].Aidx) + // Creator should be present and set to address A + require.Equal(t, addrA, prs[0].Creator) + require.Equal(t, addrA, prs[1].Creator) + require.Equal(t, expectedRound, prs[0].Round) // db round (inside resources) + require.Equal(t, expectedRound, prs[1].Round) + require.Equal(t, resDataWithParamsA0AcctB, prs[0].Data) + require.Equal(t, resDataWithParamsA1AcctB, prs[1].Data) + require.Equal(t, expectedRound, rnd) // db round (from the return) + + // Set limit to 1, should return only 1 resource + prs, rnd, err = aor.LookupLimitedResources(addrB, 0, 1, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, 1, len(prs)) + require.Equal(t, aidxResA0, prs[0].Aidx) + require.Equal(t, addrA, prs[0].Creator) + require.Equal(t, expectedRound, prs[0].Round) // db round (inside resources) + require.Equal(t, resDataWithParamsA0AcctB, prs[0].Data) + require.Equal(t, expectedRound, rnd) // db round (from the return) + + // Delete app owner for A-1 + _, err = aow.DeleteCreatable(aidxResA1, basics.AppCreatable) + require.NoError(t, err) + + // Set min to 1, should return only 1 resource (index 1) + prs, rnd, err = aor.LookupLimitedResources(addrB, 1, 1, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, 1, len(prs)) + require.Equal(t, aidxResA2, prs[0].Aidx) + require.Equal(t, addrA, prs[0].Creator) + require.Equal(t, expectedRound, prs[0].Round) // db round (inside resources) + require.Equal(t, resDataWithParamsA1AcctB, prs[0].Data) + require.Equal(t, expectedRound, rnd) // db round (from the return) + + // Delete both resource creatables + rowsAffected, err := aow.DeleteCreatable(aidxResA0, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + rowsAffected, err = aow.DeleteCreatable(aidxResA2, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + _, err = aow.DeleteResource(refAccA, aidxResA0) + require.NoError(t, err) + _, err = aow.DeleteResource(refAccA, aidxResA2) + require.NoError(t, err) + + // Account A should have no resources, account B should have 2 resources without a creator/params + prs, rnd, err = aor.LookupLimitedResources(addrA, 0, 2, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, 0, len(prs)) + prs, rnd, err = aor.LookupLimitedResources(addrB, 0, 2, basics.AssetCreatable) + require.NoError(t, err) + require.Equal(t, 2, len(prs)) + require.Equal(t, aidxResA0, prs[0].Aidx) + require.Equal(t, aidxResA2, prs[1].Aidx) + require.True(t, prs[0].Creator.IsZero()) + require.True(t, prs[1].Creator.IsZero()) + require.Equal(t, expectedRound, prs[0].Round) // db round (inside resources) + require.Equal(t, expectedRound, prs[1].Round) + // Note these directly reflect what was inserted into resources table (no creator/params) + require.Equal(t, resDataA0AcctB, prs[0].Data) + require.Equal(t, resDataA2AcctB, prs[1].Data) +} + func CustomTestAppKVCrud(t *customT) { aow, err := t.db.MakeAccountsOptimizedWriter(true, true, true, false) require.NoError(t, err) @@ -259,6 +454,7 @@ func CustomTestAppKVCrud(t *customT) { require.NoError(t, err) // resource resDataA0 := trackerdb.ResourcesData{} + resDataA0.SetAssetHolding(basics.AssetHolding{Amount: 0}) aidxResA0 := basics.CreatableIndex(0) refResA0, err := aow.InsertResource(refAccA, aidxResA0, resDataA0) require.NoError(t, err) @@ -333,12 +529,14 @@ func CustomTestCreatablesCrud(t *customT) { // resource A-0 resDataA0 := trackerdb.ResourcesData{} + resDataA0.SetAssetHolding(basics.AssetHolding{Amount: 0}) aidxResA0 := basics.CreatableIndex(0) _, err = aow.InsertResource(refAccA, aidxResA0, resDataA0) require.NoError(t, err) // resource A-1 resDataA1 := trackerdb.ResourcesData{} + resDataA1.SetAssetHolding(basics.AssetHolding{Amount: 0}) aidxResA1 := basics.CreatableIndex(1) _, err = aow.InsertResource(refAccA, aidxResA1, resDataA1) require.NoError(t, err) diff --git a/ledger/store/trackerdb/testsuite/sqlitedb_test.go b/ledger/store/trackerdb/testsuite/sqlitedb_test.go index 5a0d942ab8..3b76e8d04d 100644 --- a/ledger/store/trackerdb/testsuite/sqlitedb_test.go +++ b/ledger/store/trackerdb/testsuite/sqlitedb_test.go @@ -38,6 +38,9 @@ func TestSqliteDB(t *testing.T) { return db } + // Run CustomTestResourcesQueryAllLimited, which is not supported by the k-v store implementation + registerTest("resources-query-all-limited", CustomTestResourcesQueryAllLimited) + // run the suite runGenericTestsWithDB(t, dbFactory) } diff --git a/ledger/store/trackerdb/version.go b/ledger/store/trackerdb/version.go index bf81dce82d..4cb343f335 100644 --- a/ledger/store/trackerdb/version.go +++ b/ledger/store/trackerdb/version.go @@ -19,4 +19,4 @@ package trackerdb // AccountDBVersion is the database version that this binary would know how to support and how to upgrade to. // details about the content of each of the versions can be found in the upgrade functions upgradeDatabaseSchemaXXXX // and their descriptions. -var AccountDBVersion = int32(10) +var AccountDBVersion = int32(11) diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 720e067019..e72b0588d6 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -664,6 +664,15 @@ func (c *Client) AccountInformation(account string, includeCreatables bool) (res return } +// AccountAssetsInformation returns the assets held by an account, including asset params for non-deleted assets. +func (c *Client) AccountAssetsInformation(account string, next *string, limit *uint64) (resp model.AccountAssetsInformationResponse, err error) { + algod, err := c.ensureAlgodClient() + if err == nil { + resp, err = algod.AccountAssetsInformation(account, next, limit) + } + return +} + // AccountApplicationInformation gets account information about a given app. func (c *Client) AccountApplicationInformation(accountAddress string, applicationID uint64) (resp model.AccountApplicationResponse, err error) { algod, err := c.ensureAlgodClient() diff --git a/test/scripts/e2e_subs/goal-account-asset.sh b/test/scripts/e2e_subs/goal-account-asset.sh index 9df881ca59..3366838d9e 100755 --- a/test/scripts/e2e_subs/goal-account-asset.sh +++ b/test/scripts/e2e_subs/goal-account-asset.sh @@ -41,9 +41,90 @@ ${gcmd} asset optin --account ${ACCOUNTB} --assetid ${ASSET_D_ID} --signer ${ACC # displays held assets ${gcmd} account info -a ${ACCOUNTB} +# wait a few rounds for the asset optins to get into the db, since the account asset info endpoint +# does not look at in memory deltas +${gcmd} clerk send -a 0 -f ${ACCOUNTA} -t ${ACCOUNTB} +${gcmd} clerk send -a 0 -f ${ACCOUNTA} -t ${ACCOUNTB} +${gcmd} clerk send -a 0 -f ${ACCOUNTA} -t ${ACCOUNTB} + +# query account assets w/ details, (1) +RES=$(${gcmd} account assetdetails -a ${ACCOUNTB}) +if [[ ${RES} != *"Account: ${ACCOUNTB}"* ]]; then + date '+goal-account-asset-test assetdetails (1) should be for correct account %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_A_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (1) should contain asset A %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_B_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (1) should contain asset B %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_C_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (1) should contain asset C %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_D_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (1) should contain asset D %Y%m%d_%H%M%S' + false +fi + +# query account assets w/ details, limit 2, next set to asset B, (2) +RES=$(${gcmd} account assetdetails -a ${ACCOUNTB} -l 2 -n ${ASSET_B_ID}) +if [[ ${RES} != *"Account: ${ACCOUNTB}"* ]]; then + date '+goal-account-asset-test assetdetails (2) should be for correct account %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} == *"Asset ID: ${ASSET_A_ID}"* ]]; then + date '+goal-account-asset-test assetdetails (2) should not contain asset A %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} == *"Asset ID: ${ASSET_B_ID}"* ]]; then + date '+goal-account-asset-test assetdetails (2) should not contain asset B %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_C_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (2) should contain asset C %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_D_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (2) should contain asset D %Y%m%d_%H%M%S' + false +fi + # delete one of the asset ${gcmd} asset destroy --assetid ${ASSET_B_ID} --creator ${ACCOUNTA} --signer ${ACCOUNTA} +# wait a few rounds for the deletion to get into the db +${gcmd} clerk send -a 0 -f ${ACCOUNTA} -t ${ACCOUNTB} +${gcmd} clerk send -a 0 -f ${ACCOUNTA} -t ${ACCOUNTB} +${gcmd} clerk send -a 0 -f ${ACCOUNTA} -t ${ACCOUNTB} + +# query account assets w/ details after deletion, (3) +RES=$(${gcmd} account assetdetails -a ${ACCOUNTB}) +if [[ ${RES} != *"Account: ${ACCOUNTB}"* ]]; then + date '+goal-account-asset-test assetdetails (3) should be for correct account %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_A_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (3) should contain asset A %Y%m%d_%H%M%S' + false +fi +# ensure asset B is still present, but its params are unavailable +if [[ ${RES} != *"Asset ID: ${ASSET_B_ID}"$'\n'" Amount (without formatting): 0"* ]]; then + date '+goal-account-asset-test assetdetails (3) should contain asset B without asset params %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_C_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (3) should contain asset C %Y%m%d_%H%M%S' + false +fi +if [[ ${RES} != *"Asset ID: ${ASSET_D_ID}"$'\n'" Amount: 0"* ]]; then + date '+goal-account-asset-test assetdetails (3) should contain asset D %Y%m%d_%H%M%S' + false +fi + # check account info display RES=$(${gcmd} account info -a ${ACCOUNTB}) From d3114e8e464609a224df67efee4ae036cae251d9 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 7 May 2024 10:49:33 -0400 Subject: [PATCH 115/218] Tests: Stabilize nightly build (#5992) --- test/e2e-go/features/incentives/payouts_test.go | 2 +- test/e2e-go/features/incentives/suspension_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e-go/features/incentives/payouts_test.go b/test/e2e-go/features/incentives/payouts_test.go index 7c4cc32aac..76a7caa004 100644 --- a/test/e2e-go/features/incentives/payouts_test.go +++ b/test/e2e-go/features/incentives/payouts_test.go @@ -48,7 +48,7 @@ func TestBasicPayouts(t *testing.T) { var fixture fixtures.RestClientFixture // Make the seed lookback shorter, otherwise we need to wait 320 rounds to become IncentiveEligible. const lookback = 32 - fixture.FasterConsensus(protocol.ConsensusFuture, time.Second/2, 32) + fixture.FasterConsensus(protocol.ConsensusFuture, time.Second, 32) fmt.Printf("lookback is %d\n", lookback) fixture.Setup(t, filepath.Join("nettemplates", "Payouts.json")) defer fixture.Shutdown() diff --git a/test/e2e-go/features/incentives/suspension_test.go b/test/e2e-go/features/incentives/suspension_test.go index 9be9cade6b..2b0f451c1b 100644 --- a/test/e2e-go/features/incentives/suspension_test.go +++ b/test/e2e-go/features/incentives/suspension_test.go @@ -53,7 +53,7 @@ func TestBasicSuspension(t *testing.T) { var fixture fixtures.RestClientFixture // Speed up rounds, but keep long lookback, so 20% node has a chance to get // back online after being suspended. - fixture.FasterConsensus(protocol.ConsensusFuture, time.Second/2, 320) + fixture.FasterConsensus(protocol.ConsensusFuture, time.Second, 320) fixture.Setup(t, filepath.Join("nettemplates", "Suspension.json")) defer fixture.Shutdown() From e07ad9c8e78f967f1d290c113a0cc684d74148ff Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 7 May 2024 10:49:54 -0400 Subject: [PATCH 116/218] tests: move TestCheckSpender to transactions package (#5966) --- data/transactions/payment.go | 4 +- data/transactions/payment_test.go | 64 +++++++++++++++++++++++++++++++ data/transactions/transaction.go | 2 +- ledger/apply/payment_test.go | 56 --------------------------- 4 files changed, 67 insertions(+), 59 deletions(-) diff --git a/data/transactions/payment.go b/data/transactions/payment.go index d51e4ed1d6..9a11730fc3 100644 --- a/data/transactions/payment.go +++ b/data/transactions/payment.go @@ -37,8 +37,8 @@ type PaymentTxnFields struct { CloseRemainderTo basics.Address `codec:"close"` } -// CheckSpender performs some stateless checks on the Sender of a pay transaction -func (payment PaymentTxnFields) CheckSpender(header Header, spec SpecialAddresses, proto config.ConsensusParams) error { +// checkSpender performs some stateless checks on the Sender of a pay transaction +func (payment PaymentTxnFields) checkSpender(header Header, spec SpecialAddresses, proto config.ConsensusParams) error { if header.Sender == payment.CloseRemainderTo { return fmt.Errorf("transaction cannot close account to its sender %v", header.Sender) } diff --git a/data/transactions/payment_test.go b/data/transactions/payment_test.go index e2583c429c..ae9ecfab64 100644 --- a/data/transactions/payment_test.go +++ b/data/transactions/payment_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" @@ -68,3 +69,66 @@ func TestAlgosEncoding(t *testing.T) { panic("decode of bool into MicroAlgos succeeded") } } + +func TestCheckSpender(t *testing.T) { + partitiontest.PartitionTest(t) + + v7 := config.Consensus[protocol.ConsensusV7] + v39 := config.Consensus[protocol.ConsensusV39] + vFuture := config.Consensus[protocol.ConsensusFuture] + + secretSrc := keypair() + src := basics.Address(secretSrc.SignatureVerifier) + + secretDst := keypair() + dst := basics.Address(secretDst.SignatureVerifier) + + tx := Transaction{ + Type: protocol.PaymentTx, + Header: Header{ + Sender: src, + Fee: basics.MicroAlgos{Raw: 1}, + FirstValid: basics.Round(100), + LastValid: basics.Round(1000), + }, + PaymentTxnFields: PaymentTxnFields{ + Receiver: dst, + Amount: basics.MicroAlgos{Raw: uint64(50)}, + }, + } + + feeSink := basics.Address{0x01} + poolAddr := basics.Address{0x02} + var spec = SpecialAddresses{ + FeeSink: feeSink, + RewardsPool: poolAddr, + } + + tx.Sender = feeSink + require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7), + "to non incentive pool address") + require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39), + "to non incentive pool address") + require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture), + "cannot spend from fee sink") + + tx.Receiver = poolAddr + require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7)) + require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39)) + require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture), + "cannot spend from fee sink") + + tx.CloseRemainderTo = poolAddr + require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7), + "cannot close fee sink") + require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39), + "cannot close fee sink") + require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture), + "cannot spend from fee sink") + + // When not sending from fee sink, everything's fine + tx.Sender = src + require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7)) + require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39)) + require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture)) +} diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 04a258633f..06ae38c0d6 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -352,7 +352,7 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa switch tx.Type { case protocol.PaymentTx: // in case that the fee sink is spending, check that this spend is to a valid address - err := tx.CheckSpender(tx.Header, spec, proto) + err := tx.checkSpender(tx.Header, spec, proto) if err != nil { return err } diff --git a/ledger/apply/payment_test.go b/ledger/apply/payment_test.go index 6e756350fb..e7d23721b6 100644 --- a/ledger/apply/payment_test.go +++ b/ledger/apply/payment_test.go @@ -108,62 +108,6 @@ func TestPaymentApply(t *testing.T) { require.NoError(t, err) } -func TestCheckSpender(t *testing.T) { - partitiontest.PartitionTest(t) - - v7 := config.Consensus[protocol.ConsensusV7] - v39 := config.Consensus[protocol.ConsensusV39] - vFuture := config.Consensus[protocol.ConsensusFuture] - - secretSrc := keypair() - src := basics.Address(secretSrc.SignatureVerifier) - - secretDst := keypair() - dst := basics.Address(secretDst.SignatureVerifier) - - tx := transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: src, - Fee: basics.MicroAlgos{Raw: 1}, - FirstValid: basics.Round(100), - LastValid: basics.Round(1000), - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: dst, - Amount: basics.MicroAlgos{Raw: uint64(50)}, - }, - } - - tx.Sender = feeSink - require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7), - "to non incentive pool address") - require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39), - "to non incentive pool address") - require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture), - "cannot spend from fee sink") - - tx.Receiver = poolAddr - require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7)) - require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39)) - require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture), - "cannot spend from fee sink") - - tx.CloseRemainderTo = poolAddr - require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7), - "cannot close fee sink") - require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39), - "cannot close fee sink") - require.ErrorContains(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture), - "cannot spend from fee sink") - - // When not sending from fee sink, everything's fine - tx.Sender = src - require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v7)) - require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, v39)) - require.NoError(t, tx.PaymentTxnFields.CheckSpender(tx.Header, spec, vFuture)) -} - func TestPaymentValidation(t *testing.T) { partitiontest.PartitionTest(t) From 6c91f521ac21c385e290dbdd106cc0e55779d07d Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Tue, 7 May 2024 12:17:29 -0400 Subject: [PATCH 117/218] crypto: convert BatchVerifier to interface (#5988) --- crypto/batchverifier.go | 62 +++++++++++++++++++++------- crypto/batchverifier_test.go | 2 +- crypto/multisig.go | 2 +- crypto/onetimesig.go | 19 ++++++++- data/transactions/verify/txn.go | 8 ++-- data/transactions/verify/txnBatch.go | 2 +- 6 files changed, 71 insertions(+), 24 deletions(-) diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index 0281e86e87..661e382898 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -50,14 +50,20 @@ import ( ) // BatchVerifier enqueues signatures to be validated in batch. -type BatchVerifier struct { +type BatchVerifier interface { + EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) + GetNumberOfEnqueuedSignatures() int + Verify() error + VerifyWithFeedback() (failed []bool, err error) +} + +type cgoBatchVerifier struct { messages []Hashable // contains a slice of messages to be hashed. Each message is varible length publicKeys []SignatureVerifier // contains a slice of public keys. Each individual public key is 32 bytes. signatures []Signature // contains a slice of signatures keys. Each individual signature is 64 bytes. + useSingle bool } -const minBatchVerifierAlloc = 16 - // Batch verifications errors var ( ErrBatchHasFailedSigs = errors.New("At least one signature didn't pass verification") @@ -69,27 +75,31 @@ func ed25519_randombytes_unsafe(p unsafe.Pointer, len C.size_t) { RandBytes(randBuf) } -// MakeBatchVerifier creates a BatchVerifier instance. -func MakeBatchVerifier() *BatchVerifier { +const minBatchVerifierAlloc = 16 +const useSingleVerifierDefault = true + +// MakeBatchVerifier creates a BatchVerifier instance with the provided options. +func MakeBatchVerifier() BatchVerifier { return MakeBatchVerifierWithHint(minBatchVerifierAlloc) } -// MakeBatchVerifierWithHint creates a BatchVerifier instance. This function pre-allocates +// MakeBatchVerifierWithHint creates a cgoBatchVerifier instance. This function pre-allocates // amount of free space to enqueue signatures without expanding -func MakeBatchVerifierWithHint(hint int) *BatchVerifier { +func MakeBatchVerifierWithHint(hint int) BatchVerifier { // preallocate enough storage for the expected usage. We will reallocate as needed. if hint < minBatchVerifierAlloc { hint = minBatchVerifierAlloc } - return &BatchVerifier{ + return &cgoBatchVerifier{ messages: make([]Hashable, 0, hint), publicKeys: make([]SignatureVerifier, 0, hint), signatures: make([]Signature, 0, hint), + useSingle: useSingleVerifierDefault, } } // EnqueueSignature enqueues a signature to be enqueued -func (b *BatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) { +func (b *cgoBatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) { // do we need to reallocate ? if len(b.messages) == cap(b.messages) { b.expand() @@ -99,7 +109,7 @@ func (b *BatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message b.signatures = append(b.signatures, sig) } -func (b *BatchVerifier) expand() { +func (b *cgoBatchVerifier) expand() { messages := make([]Hashable, len(b.messages), len(b.messages)*2) publicKeys := make([]SignatureVerifier, len(b.publicKeys), len(b.publicKeys)*2) signatures := make([]Signature, len(b.signatures), len(b.signatures)*2) @@ -112,12 +122,12 @@ func (b *BatchVerifier) expand() { } // GetNumberOfEnqueuedSignatures returns the number of signatures currently enqueued into the BatchVerifier -func (b *BatchVerifier) GetNumberOfEnqueuedSignatures() int { +func (b *cgoBatchVerifier) GetNumberOfEnqueuedSignatures() int { return len(b.messages) } // Verify verifies that all the signatures are valid. in that case nil is returned -func (b *BatchVerifier) Verify() error { +func (b *cgoBatchVerifier) Verify() error { _, err := b.VerifyWithFeedback() return err } @@ -126,11 +136,15 @@ func (b *BatchVerifier) Verify() error { // if all sigs are valid, nil will be returned for err (failed will have all false) // if some signatures are invalid, true will be set in failed at the corresponding indexes, and // ErrBatchVerificationFailed for err -func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) { +func (b *cgoBatchVerifier) VerifyWithFeedback() (failed []bool, err error) { if len(b.messages) == 0 { return nil, nil } + if b.useSingle { + return b.singleVerify() + } + const estimatedMessageSize = 64 msgLengths := make([]uint64, 0, len(b.messages)) var messages = make([]byte, 0, len(b.messages)*estimatedMessageSize) @@ -141,17 +155,33 @@ func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) { msgLengths = append(msgLengths, uint64(len(messages)-lenWas)) lenWas = len(messages) } - allValid, failed := batchVerificationImpl(messages, msgLengths, b.publicKeys, b.signatures) + allValid, failed := cgoBatchVerificationImpl(messages, msgLengths, b.publicKeys, b.signatures) if allValid { return failed, nil } return failed, ErrBatchHasFailedSigs } -// batchVerificationImpl invokes the ed25519 batch verification algorithm. +func (b *cgoBatchVerifier) singleVerify() (failed []bool, err error) { + failed = make([]bool, len(b.messages)) + var containsFailed bool + + for i := range b.messages { + failed[i] = !ed25519Verify(ed25519PublicKey(b.publicKeys[i]), HashRep(b.messages[i]), ed25519Signature(b.signatures[i])) + if failed[i] { + containsFailed = true + } + } + if containsFailed { + return failed, ErrBatchHasFailedSigs + } + return failed, nil +} + +// cgoBatchVerificationImpl invokes the ed25519 batch verification algorithm. // it returns true if all the signatures were authentically signed by the owners // otherwise, returns false, and sets the indexes of the failed sigs in failed -func batchVerificationImpl(messages []byte, msgLengths []uint64, publicKeys []SignatureVerifier, signatures []Signature) (allSigsValid bool, failed []bool) { +func cgoBatchVerificationImpl(messages []byte, msgLengths []uint64, publicKeys []SignatureVerifier, signatures []Signature) (allSigsValid bool, failed []bool) { numberOfSignatures := len(msgLengths) valid := make([]C.int, numberOfSignatures) diff --git a/crypto/batchverifier_test.go b/crypto/batchverifier_test.go index 2b8fcec0e9..3922105aa5 100644 --- a/crypto/batchverifier_test.go +++ b/crypto/batchverifier_test.go @@ -171,7 +171,7 @@ func BenchmarkBatchVerifierBigWithInvalid(b *testing.B) { failed, err := bv.VerifyWithFeedback() if err != nil { for i, f := range failed { - if bv.signatures[i] == badSig { + if bv.(*cgoBatchVerifier).signatures[i] == badSig { require.True(b, f) } else { require.False(b, f) diff --git a/crypto/multisig.go b/crypto/multisig.go index d5529c149f..f17833f1a0 100644 --- a/crypto/multisig.go +++ b/crypto/multisig.go @@ -240,7 +240,7 @@ func MultisigVerify(msg Hashable, addr Digest, sig MultisigSig) (err error) { // MultisigBatchPrep performs checks on the assembled MultisigSig and adds to the batch. // The caller must call batchVerifier.verify() to verify it. -func MultisigBatchPrep(msg Hashable, addr Digest, sig MultisigSig, batchVerifier *BatchVerifier) error { +func MultisigBatchPrep(msg Hashable, addr Digest, sig MultisigSig, batchVerifier BatchVerifier) error { // short circuit: if msig doesn't have subsigs or if Subsigs are empty // then terminate (the upper layer should now verify the unisig) if (len(sig.Subsigs) == 0 || sig.Subsigs[0] == MultisigSubsig{}) { diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index bc11070125..d05ccaa961 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -324,6 +324,23 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message Batch: id.Batch, } + if !useSingleVerifierDefault { + return v.batchVerify(batchID, offsetID, message, sig) + } + + if !ed25519Verify(ed25519PublicKey(v), HashRep(batchID), sig.PK2Sig) { + return false + } + if !ed25519Verify(batchID.SubKeyPK, HashRep(offsetID), sig.PK1Sig) { + return false + } + if !ed25519Verify(offsetID.SubKeyPK, HashRep(message), sig.Sig) { + return false + } + return true +} + +func (v OneTimeSignatureVerifier) batchVerify(batchID OneTimeSignatureSubkeyBatchID, offsetID OneTimeSignatureSubkeyOffsetID, message Hashable, sig OneTimeSignature) bool { // serialize encoded batchID, offsetID, message into a continuous memory buffer with the layout // hashRep(batchID)... hashRep(offsetID)... hashRep(message)... const estimatedSize = 256 @@ -336,7 +353,7 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message messageBuffer = HashRepToBuff(message, messageBuffer) messageLen := uint64(len(messageBuffer)) - offsetIDLen - batchIDLen msgLengths := []uint64{batchIDLen, offsetIDLen, messageLen} - allValid, _ := batchVerificationImpl( + allValid, _ := cgoBatchVerificationImpl( messageBuffer, msgLengths, []PublicKey{PublicKey(v), PublicKey(batchID.SubKeyPK), PublicKey(offsetID.SubKeyPK)}, diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 640189cc57..a345c679af 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -163,7 +163,7 @@ func (g *GroupContext) Equal(other *GroupContext) bool { // txnBatchPrep verifies a SignedTxn having no obviously inconsistent data. // Block-assembly time checks of LogicSig and accounting rules may still block the txn. // It is the caller responsibility to call batchVerifier.Verify(). -func txnBatchPrep(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) *TxGroupError { +func txnBatchPrep(gi int, groupCtx *GroupContext, verifier crypto.BatchVerifier) *TxGroupError { s := &groupCtx.signedGroupTxns[gi] if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) { return &TxGroupError{err: errRekeyingNotSupported, GroupIndex: gi, Reason: TxGroupErrorReasonGeneric} @@ -206,7 +206,7 @@ func txnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader // txnGroupBatchPrep verifies a []SignedTxn having no obviously inconsistent data. // it is the caller responsibility to call batchVerifier.Verify() -func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) (*GroupContext, error) { +func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier crypto.BatchVerifier, evalTracer logic.EvalTracer) (*GroupContext, error) { groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger, evalTracer) if err != nil { return nil, err @@ -287,7 +287,7 @@ func checkTxnSigTypeCounts(s *transactions.SignedTxn, groupIndex int) (sigType s } // stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification. -func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) *TxGroupError { +func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier crypto.BatchVerifier) *TxGroupError { s := &groupCtx.signedGroupTxns[gi] sigType, err := checkTxnSigTypeCounts(s, gi) if err != nil { @@ -340,7 +340,7 @@ func LogicSigSanityCheck(gi int, groupCtx *GroupContext) error { // logicSigSanityCheckBatchPrep checks that the signature is valid and that the program is basically well formed. // It does not evaluate the logic. // it is the caller responsibility to call batchVerifier.Verify() -func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) error { +func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier crypto.BatchVerifier) error { if groupCtx.consensusParams.LogicSigVersion == 0 { return errors.New("LogicSig not enabled") } diff --git a/data/transactions/verify/txnBatch.go b/data/transactions/verify/txnBatch.go index 4b309f6946..8619208da8 100644 --- a/data/transactions/verify/txnBatch.go +++ b/data/transactions/verify/txnBatch.go @@ -170,7 +170,7 @@ func (tbp *txnSigBatchProcessor) ProcessBatch(txns []execpool.InputJob) { tbp.postProcessVerifiedJobs(ctx, failed, err) } -func (tbp *txnSigBatchProcessor) preProcessUnverifiedTxns(uTxns []execpool.InputJob) (batchVerifier *crypto.BatchVerifier, ctx interface{}) { +func (tbp *txnSigBatchProcessor) preProcessUnverifiedTxns(uTxns []execpool.InputJob) (batchVerifier crypto.BatchVerifier, ctx interface{}) { batchVerifier = crypto.MakeBatchVerifier() bl := makeBatchLoad(len(uTxns)) // TODO: separate operations here, and get the sig verification inside the LogicSig to the batch here From 2758b29bd1f83e4fe4402e45a21ef27a10f277e7 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Wed, 8 May 2024 11:54:27 -0400 Subject: [PATCH 118/218] Build: Go 1.21 Support (#5994) Co-authored-by: Alex Wied --- .github/workflows/reviewdog.yml | 8 +- .golangci.yml | 8 +- Dockerfile | 2 +- cmd/goal/account.go | 10 +- cmd/partitiontest_linter/go.mod | 11 +- cmd/partitiontest_linter/go.sum | 13 +- crypto/secp256k1/secp256_test.go | 3 +- data/pools/transactionPool.go | 4 +- data/pools/transactionPool_test.go | 16 +- data/transactions/logic/opcodes.go | 5 +- go.mod | 88 ++- go.sum | 504 ++++-------------- ledger/acctdeltas_test.go | 10 +- ledger/eval/txntracer.go | 2 + .../store/trackerdb/dualdriver/dualdriver.go | 10 +- network/p2p/peerstore/peerstore_test.go | 6 +- scripts/buildtools/versions | 4 +- scripts/get_golang_version.sh | 6 +- tools/block-generator/go.mod | 82 ++- tools/block-generator/go.sum | 501 ++++------------- tools/x-repo-types/go.mod | 4 +- 21 files changed, 326 insertions(+), 971 deletions(-) diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index c2d336194f..efa0d75217 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -17,10 +17,10 @@ jobs: - name: Make libsodium.a run: sudo mv /usr/bin/go /usr/bin/go.bak && make crypto/libs/linux/amd64/lib/libsodium.a && sudo mv /usr/bin/go.bak /usr/bin/go - name: reviewdog-golangci-lint - uses: reviewdog/action-golangci-lint@v2.3.1 + uses: reviewdog/action-golangci-lint@v2.6.1 with: go_version_file: go.mod - golangci_lint_version: "v1.53.2" + golangci_lint_version: "v1.58.0" golangci_lint_flags: "-c .golangci.yml --allow-parallel-runners" reporter: "github-pr-check" tool_name: "Lint Errors" @@ -63,13 +63,13 @@ jobs: run: | cd cicdtmp/golangci-lint git clone https://github.com/golangci/golangci-lint.git . - git checkout tags/v1.53.2 + git checkout tags/v1.58.0 CGO_ENABLED=true go build -trimpath -o golangci-lint-cgo ./cmd/golangci-lint ./golangci-lint-cgo --version cd ../../ - name: Install reviewdog run: | - curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/v0.14.1/install.sh | sh -s + curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/v0.17.4/install.sh | sh -s reviewdog --version - name: Build custom linters run: | diff --git a/.golangci.yml b/.golangci.yml index 24dba2d0c4..db13e1d65a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -36,7 +36,9 @@ linters-settings: - (*github.com/spf13/pflag.FlagSet).MarkDeprecated - (*github.com/spf13/pflag.FlagSet).MarkShorthandDeprecated govet: - check-shadowing: true + # Enables these linters in addition to the default ones. + enable: + - shadow settings: shadow: # explanation of strict vs non-strict: @@ -131,6 +133,10 @@ issues: linters: - staticcheck text: "SA4006: this value" # of X is never used + - path: _test\.go + linters: + - revive + text: "dot-imports: should not use dot imports" - linters: - staticcheck text: "SA1019: rand*" diff --git a/Dockerfile b/Dockerfile index 189b9501f0..99a30949fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 as builder -ARG GO_VERSION="1.20.14" +ARG GO_VERSION="1.21.10" ARG CHANNEL ARG URL diff --git a/cmd/goal/account.go b/cmd/goal/account.go index c4ddd96fa2..67d382bf45 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -69,7 +69,7 @@ var ( mnemonic string dumpOutFile string listAccountInfo bool - onlyShowAssetIds bool + onlyShowAssetIDs bool partKeyIDToDelete string next string @@ -138,7 +138,7 @@ func init() { // Info flags infoCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to look up (required)") infoCmd.MarkFlagRequired("address") - infoCmd.Flags().BoolVar(&onlyShowAssetIds, "onlyShowAssetIds", false, "Only show ASA IDs and not pull asset metadata") + infoCmd.Flags().BoolVar(&onlyShowAssetIDs, "onlyShowAssetIDs", false, "Only show ASA IDs and not pull asset metadata") // Asset details flags assetDetailsCmd.Flags().StringVarP(&accountAddress, "address", "a", "", "Account address to look up (required)") @@ -589,14 +589,14 @@ var infoCmd = &cobra.Command{ reportErrorf(errorRequestFail, err) } - hasError := printAccountInfo(client, accountAddress, onlyShowAssetIds, response) + hasError := printAccountInfo(client, accountAddress, onlyShowAssetIDs, response) if hasError { os.Exit(1) } }, } -func printAccountInfo(client libgoal.Client, address string, onlyShowAssetIds bool, account model.Account) bool { +func printAccountInfo(client libgoal.Client, address string, onlyShowAssetIDs bool, account model.Account) bool { var createdAssets []model.Asset if account.CreatedAssets != nil { createdAssets = slices.Clone(*account.CreatedAssets) @@ -664,7 +664,7 @@ func printAccountInfo(client libgoal.Client, address string, onlyShowAssetIds bo fmt.Fprintln(report, "\t") } for _, assetHolding := range heldAssets { - if onlyShowAssetIds { + if onlyShowAssetIDs { fmt.Fprintf(report, "\tID %d\n", assetHolding.AssetID) continue } diff --git a/cmd/partitiontest_linter/go.mod b/cmd/partitiontest_linter/go.mod index 88ea431f95..871ea35935 100644 --- a/cmd/partitiontest_linter/go.mod +++ b/cmd/partitiontest_linter/go.mod @@ -1,9 +1,12 @@ module github.com/algorand/go-algorand/cmd/partitiontest_linter -go 1.20 +go 1.21 -require golang.org/x/sys v0.8.0 // indirect +toolchain go1.21.10 -require golang.org/x/mod v0.10.0 // indirect +require ( + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect +) -require golang.org/x/tools v0.9.3 +require golang.org/x/tools v0.20.0 diff --git a/cmd/partitiontest_linter/go.sum b/cmd/partitiontest_linter/go.sum index a5ed5c851f..9e7e462a7c 100644 --- a/cmd/partitiontest_linter/go.sum +++ b/cmd/partitiontest_linter/go.sum @@ -1,7 +1,6 @@ -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index 5da4e593ca..2ce95634bf 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -7,7 +7,6 @@ package secp256k1 import ( "bytes" "crypto/ecdsa" - "crypto/elliptic" "crypto/rand" "encoding/hex" "io" @@ -23,7 +22,7 @@ func generateKeyPair() (pubkey, privkey []byte) { if err != nil { panic(err) } - pubkey = elliptic.Marshal(S256(), key.X, key.Y) + pubkey = S256().Marshal(key.X, key.Y) privkey = make([]byte, 32) blob := key.D.Bytes() diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index 31ad805d68..687a3db80c 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -668,7 +668,7 @@ func (pool *TransactionPool) addToPendingBlockEvaluator(txgroup []transactions.S // recomputeBlockEvaluator constructs a new BlockEvaluator and feeds all // in-pool transactions to it (removing any transactions that are rejected // by the BlockEvaluator). Expects that the pool.mu mutex would be already taken. -func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transactions.Txid]ledgercore.IncludedTransactions, knownCommitted uint) (stats telemetryspec.ProcessBlockMetrics) { +func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIDs map[transactions.Txid]ledgercore.IncludedTransactions, knownCommitted uint) (stats telemetryspec.ProcessBlockMetrics) { pool.pendingBlockEvaluator = nil latest := pool.ledger.Latest() @@ -741,7 +741,7 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transact asmStats.InvalidCount++ continue } - if _, alreadyCommitted := committedTxIds[txgroup[0].ID()]; alreadyCommitted { + if _, alreadyCommitted := committedTxIDs[txgroup[0].ID()]; alreadyCommitted { asmStats.EarlyCommittedCount++ continue } diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index 72c757aa2d..01b851f86d 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -1167,24 +1167,24 @@ func BenchmarkTransactionPoolRecompute(b *testing.B) { // make args for recomputeBlockEvaluator() like OnNewBlock() would var knownCommitted uint - committedTxIds := make(map[transactions.Txid]ledgercore.IncludedTransactions) + committedTxIDs := make(map[transactions.Txid]ledgercore.IncludedTransactions) for i := 0; i < blockTxnCount; i++ { knownCommitted++ // OK to use empty IncludedTransactions: recomputeBlockEvaluator is only checking map membership - committedTxIds[signedTransactions[i].ID()] = ledgercore.IncludedTransactions{} + committedTxIDs[signedTransactions[i].ID()] = ledgercore.IncludedTransactions{} } - b.Logf("Made transactionPool with %d signedTransactions, %d committedTxIds, %d knownCommitted", - len(signedTransactions), len(committedTxIds), knownCommitted) + b.Logf("Made transactionPool with %d signedTransactions, %d committedTxIDs, %d knownCommitted", + len(signedTransactions), len(committedTxIDs), knownCommitted) b.Logf("transactionPool pendingTxGroups %d rememberedTxGroups %d", len(transactionPool.pendingTxGroups), len(transactionPool.rememberedTxGroups)) - return transactionPool, committedTxIds, knownCommitted + return transactionPool, committedTxIDs, knownCommitted } transactionPool := make([]*TransactionPool, b.N) - committedTxIds := make([]map[transactions.Txid]ledgercore.IncludedTransactions, b.N) + committedTxIDs := make([]map[transactions.Txid]ledgercore.IncludedTransactions, b.N) knownCommitted := make([]uint, b.N) for i := 0; i < b.N; i++ { - transactionPool[i], committedTxIds[i], knownCommitted[i] = setupPool() + transactionPool[i], committedTxIDs[i], knownCommitted[i] = setupPool() } time.Sleep(time.Second) runtime.GC() @@ -1202,7 +1202,7 @@ func BenchmarkTransactionPoolRecompute(b *testing.B) { } b.ResetTimer() for i := 0; i < b.N; i++ { - transactionPool[i].recomputeBlockEvaluator(committedTxIds[i], knownCommitted[i]) + transactionPool[i].recomputeBlockEvaluator(committedTxIDs[i], knownCommitted[i]) } b.StopTimer() if profF != nil { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index ae152bf919..bb5992ae44 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -17,6 +17,7 @@ package logic import ( + "cmp" "fmt" "strconv" "strings" @@ -836,8 +837,8 @@ func OpcodesByVersion(version uint64) []OpSpec { } } result := maps.Values(subv) - slices.SortFunc(result, func(a, b OpSpec) bool { - return a.Opcode < b.Opcode + slices.SortFunc(result, func(a, b OpSpec) int { + return cmp.Compare(a.Opcode, b.Opcode) }) return result } diff --git a/go.mod b/go.mod index 989a3021fb..c15195b54b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/algorand/go-algorand -go 1.20 +go 1.21 + +toolchain go1.21.10 require ( github.com/DataDog/zstd v1.5.2 @@ -23,27 +25,27 @@ require ( github.com/getkin/kin-openapi v0.107.0 github.com/gofrs/flock v0.7.0 github.com/golang/snappy v0.0.4 - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.8.0 github.com/jmoiron/sqlx v1.2.0 github.com/karalabe/usb v0.0.2 github.com/labstack/echo/v4 v4.9.1 - github.com/libp2p/go-libp2p v0.29.1 - github.com/libp2p/go-libp2p-pubsub v0.9.3 + github.com/libp2p/go-libp2p v0.33.2 + github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libp2p/go-yamux/v4 v4.0.1 github.com/mattn/go-sqlite3 v1.14.16 - github.com/miekg/dns v1.1.55 - github.com/multiformats/go-multiaddr v0.10.1 + github.com/miekg/dns v1.1.58 + github.com/multiformats/go-multiaddr v0.12.3 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olivere/elastic v6.2.14+incompatible github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.17.0 - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 - golang.org/x/sync v0.3.0 - golang.org/x/sys v0.15.0 + golang.org/x/crypto v0.19.0 + golang.org/x/exp v0.0.0-20240213143201-ec583247a57a + golang.org/x/sync v0.6.0 + golang.org/x/sys v0.17.0 golang.org/x/text v0.14.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 pgregory.net/rapid v0.6.2 @@ -68,7 +70,7 @@ require ( 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/flynn/noise v1.1.0 // indirect github.com/fortytw2/leaktest v1.3.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -77,14 +79,12 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // 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/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/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect + github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect @@ -93,25 +93,23 @@ require ( 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/klauspost/compress v1.17.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // 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/labstack/gommon v0.4.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-asn-util v0.3.0 // indirect + github.com/libp2p/go-libp2p-asn-util v0.4.1 // 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-reuseport v0.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mattn/go-isatty v0.0.20 // 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 @@ -124,41 +122,39 @@ require ( 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-multistream v0.5.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/onsi/ginkgo/v2 v2.11.0 // indirect - github.com/opencontainers/runtime-spec v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.15.0 // indirect + github.com/opencontainers/runtime-spec v1.2.0 // 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 - github.com/prometheus/client_golang v1.14.0 // indirect - 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/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.47.0 // indirect + github.com/prometheus/procfs v0.12.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/quic-go/quic-go v0.42.0 // indirect + github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // 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/dig v1.17.1 // indirect + go.uber.org/fx v1.20.1 // indirect + go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect - golang.org/x/tools v0.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/term v0.17.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.18.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 89b811f956..badf55816d 100644 --- a/go.sum +++ b/go.sum @@ -2,45 +2,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT 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= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -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= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= @@ -50,11 +18,6 @@ github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4K github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2k= github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/falcon v0.1.0 h1:xl832kfZ7hHG6B4p90DQynjfKFGbIUgUOnsRiMZXfAo= @@ -87,7 +50,6 @@ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx 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= @@ -96,19 +58,16 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF 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= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas= -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/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s= 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= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= @@ -151,6 +110,7 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6Uh 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/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 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= @@ -165,7 +125,6 @@ github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= @@ -175,8 +134,8 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF 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/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= @@ -193,18 +152,8 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev 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= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -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-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -213,7 +162,6 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 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= @@ -235,98 +183,62 @@ github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -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= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -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= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 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= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -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/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 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/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= +github.com/hashicorp/golang-lru/v2 v2.0.5/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/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.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= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -334,8 +246,6 @@ github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= 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= @@ -353,20 +263,13 @@ github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= @@ -379,16 +282,13 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -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/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -405,29 +305,29 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 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 v0.33.2 h1:vCdwnFxoGOXMKmaGHlDSnL4bM3fQeW8pgIa9DECnb40= +github.com/libp2p/go-libp2p v0.33.2/go.mod h1:zTeppLuCvUIkT118pFVzA8xzP/p2dJYOMApCkFh0Yww= +github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= +github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= +github.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA= +github.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= +github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= 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-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= 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= @@ -449,22 +349,20 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= 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= @@ -480,11 +378,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= @@ -497,8 +392,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 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 v0.12.3 h1:hVBXvPRcKG0w80VinQ23P5t7czWgg65BmIvQKjDydU8= +github.com/multiformats/go-multiaddr v0.12.3/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= 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= @@ -510,13 +405,11 @@ github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI1 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-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= +github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= 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= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 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= @@ -528,13 +421,15 @@ github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGe 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/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= 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/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/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= @@ -544,56 +439,35 @@ github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCr 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= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -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/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= 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/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= +github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= 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= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -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/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 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/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= +github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= +github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= 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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -625,9 +499,6 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. 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= @@ -649,7 +520,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -685,42 +555,35 @@ github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmv github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= +go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= +go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= 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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 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= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -728,51 +591,29 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -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/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= 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= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -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/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -780,48 +621,23 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn 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= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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= @@ -829,84 +645,49 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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-20200116001909-b77594299b42/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= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20210927094055-39ccf1dd6fa6/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-20211103235746-7861aae1554b/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -917,10 +698,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= @@ -929,51 +708,17 @@ golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -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= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -981,101 +726,38 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T 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= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -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= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -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= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -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= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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-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= @@ -1087,7 +769,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD 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= @@ -1101,18 +782,11 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd 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= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw= pgregory.net/rapid v0.6.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -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= diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 2f78100ccd..b16657dc64 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -508,10 +508,10 @@ func randomCreatables(numElementsPerSegement int) ([]basics.CreatableIndex, map[basics.CreatableIndex]ledgercore.ModifiedCreatable) { creatables := make(map[basics.CreatableIndex]ledgercore.ModifiedCreatable) creatablesList := make([]basics.CreatableIndex, numElementsPerSegement*10) - uniqueAssetIds := make(map[basics.CreatableIndex]bool) + uniqueAssetIDs := make(map[basics.CreatableIndex]bool) for i := 0; i < numElementsPerSegement*10; i++ { - assetIndex, mc := randomCreatable(uniqueAssetIds) + assetIndex, mc := randomCreatable(uniqueAssetIDs) creatables[assetIndex] = mc creatablesList[i] = assetIndex } @@ -519,7 +519,7 @@ func randomCreatables(numElementsPerSegement int) ([]basics.CreatableIndex, } // randomCreatable generates a random creatable. -func randomCreatable(uniqueAssetIds map[basics.CreatableIndex]bool) ( +func randomCreatable(uniqueAssetIDs map[basics.CreatableIndex]bool) ( assetIndex basics.CreatableIndex, mc ledgercore.ModifiedCreatable) { var ctype basics.CreatableType @@ -541,9 +541,9 @@ func randomCreatable(uniqueAssetIds map[basics.CreatableIndex]bool) ( var assetIdx basics.CreatableIndex for { assetIdx = basics.CreatableIndex(crypto.RandUint64() % (uint64(2) << 50)) - _, found := uniqueAssetIds[assetIdx] + _, found := uniqueAssetIDs[assetIdx] if !found { - uniqueAssetIds[assetIdx] = true + uniqueAssetIDs[assetIdx] = true break } } diff --git a/ledger/eval/txntracer.go b/ledger/eval/txntracer.go index 8736a1fdb9..96d307390a 100644 --- a/ledger/eval/txntracer.go +++ b/ledger/eval/txntracer.go @@ -32,6 +32,8 @@ import ( ) // TxnGroupDeltaWithIds associates all the Ids (group and Txn) with a single state delta object +// +//revive:disable:var-naming type TxnGroupDeltaWithIds struct { _struct struct{} `codec:",omitempty,omitemptyarray"` Ids []string diff --git a/ledger/store/trackerdb/dualdriver/dualdriver.go b/ledger/store/trackerdb/dualdriver/dualdriver.go index c7763dacef..cbcba9c480 100644 --- a/ledger/store/trackerdb/dualdriver/dualdriver.go +++ b/ledger/store/trackerdb/dualdriver/dualdriver.go @@ -458,10 +458,9 @@ func coalesceAccountRefs(primary, secondary trackerdb.AccountRef) (trackerdb.Acc } else if primary == nil && secondary == nil { // all good, ref is nil return nil, nil - } else { - // ref mismatch - return nil, ErrInconsistentResult } + // ref mismatch + return nil, ErrInconsistentResult } type onlineAccountRef struct { @@ -478,10 +477,9 @@ func coalesceOnlineAccountRefs(primary, secondary trackerdb.OnlineAccountRef) (t } else if primary == nil && secondary == nil { // all good, ref is nil return nil, nil - } else { - // ref mismatch - return nil, ErrInconsistentResult } + // ref mismatch + return nil, ErrInconsistentResult } type resourceRef struct { diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go index b4aa241ba4..9bbf2b87c6 100644 --- a/network/p2p/peerstore/peerstore_test.go +++ b/network/p2p/peerstore/peerstore_test.go @@ -59,13 +59,13 @@ func TestPeerstore(t *testing.T) { // add peer addresses var addrs []string - var peerIDS []peer.ID + var peerIDs []peer.ID for i := 0; i < 4; i++ { privKey, _, err := libp2p_crypto.GenerateEd25519Key(rand.Reader) require.NoError(t, err) peerID, err := peer.IDFromPrivateKey(privKey) require.NoError(t, err) - peerIDS = append(peerIDS, peerID) + peerIDs = append(peerIDs, peerID) maddrStr := fmt.Sprintf("/ip4/1.2.3.4/tcp/%d/p2p/%s", 4000+i, peerID.String()) addrs = append(addrs, maddrStr) } @@ -81,7 +81,7 @@ func TestPeerstore(t *testing.T) { require.Equal(t, 8, len(peers)) // remove a peer addr - ps.ClearAddrs(peerIDS[0]) + ps.ClearAddrs(peerIDs[0]) peers = ps.PeersWithAddrs() require.Equal(t, 7, len(peers)) diff --git a/scripts/buildtools/versions b/scripts/buildtools/versions index ba43b37f60..61dd519985 100644 --- a/scripts/buildtools/versions +++ b/scripts/buildtools/versions @@ -1,7 +1,7 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 -golang.org/x/tools v0.9.3 +golang.org/x/tools v0.20.0 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 -github.com/golangci/golangci-lint/cmd/golangci-lint v1.53.2 +github.com/golangci/golangci-lint/cmd/golangci-lint v1.58.0 diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh index 13dfca5bf1..256683831c 100755 --- a/scripts/get_golang_version.sh +++ b/scripts/get_golang_version.sh @@ -11,9 +11,9 @@ # 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.14 - MIN=1.20 - GO_MOD_SUPPORT=1.20 +BUILD=1.21.10 + MIN=1.21 + GO_MOD_SUPPORT=1.21 if [ "$1" = all ] then diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 33333cd47b..29bdbdb4e8 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -2,7 +2,9 @@ module github.com/algorand/go-algorand/tools/block-generator replace github.com/algorand/go-algorand => ../.. -go 1.20 +go 1.21 + +toolchain go1.21.10 require ( github.com/algorand/avm-abi v0.2.0 @@ -44,21 +46,19 @@ require ( 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/flynn/noise v1.1.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/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect + github.com/google/uuid v1.4.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/gorilla/websocket v1.5.1 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect + github.com/huin/goupnp v1.3.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 @@ -66,28 +66,26 @@ require ( 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/klauspost/compress v1.17.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // 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-libp2p v0.33.2 // indirect + github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect + github.com/libp2p/go-libp2p-pubsub v0.10.0 // 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-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect github.com/mailru/easyjson v0.7.7 // 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-isatty v0.0.20 // 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/miekg/dns v1.1.58 // 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 @@ -95,48 +93,46 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr v0.10.1 // indirect + github.com/multiformats/go-multiaddr v0.12.3 // 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-multistream v0.5.0 // 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/onsi/ginkgo/v2 v2.15.0 // indirect + github.com/opencontainers/runtime-spec v1.2.0 // 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 - github.com/prometheus/client_golang v1.14.0 // indirect - 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/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.47.0 // indirect + github.com/prometheus/procfs v0.12.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/quic-go/quic-go v0.42.0 // indirect + github.com/quic-go/webtransport-go v0.6.0 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/rogpeppe/go-internal v1.10.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 - 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/dig v1.17.1 // indirect + go.uber.org/fx v1.20.1 // indirect + go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.17.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.17.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.15.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.11.0 // indirect + golang.org/x/tools v0.18.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index 8d2ff27efb..e34dae4485 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -2,45 +2,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT 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= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -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= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= @@ -49,11 +17,6 @@ github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKz github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2k= github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/falcon v0.1.0 h1:xl832kfZ7hHG6B4p90DQynjfKFGbIUgUOnsRiMZXfAo= @@ -82,7 +45,6 @@ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx 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= @@ -90,19 +52,16 @@ github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY 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= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas= -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/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s= 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= github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= @@ -144,6 +103,7 @@ github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6Uh 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/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 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= @@ -158,7 +118,6 @@ github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= @@ -166,9 +125,10 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod 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/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 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= @@ -181,21 +141,10 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev 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= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -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-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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= @@ -213,104 +162,67 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -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= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -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= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -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/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= +github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 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/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= +github.com/hashicorp/golang-lru/v2 v2.0.5/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/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.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= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 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= @@ -326,19 +238,12 @@ github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2 github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= @@ -349,16 +254,13 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -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/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -371,29 +273,29 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= 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 v0.33.2 h1:vCdwnFxoGOXMKmaGHlDSnL4bM3fQeW8pgIa9DECnb40= +github.com/libp2p/go-libp2p v0.33.2/go.mod h1:zTeppLuCvUIkT118pFVzA8xzP/p2dJYOMApCkFh0Yww= +github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= +github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= +github.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA= +github.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= +github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= 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-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= 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= @@ -408,21 +310,19 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd 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-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-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/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/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= 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= @@ -438,11 +338,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= @@ -453,8 +350,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 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 v0.12.3 h1:hVBXvPRcKG0w80VinQ23P5t7czWgg65BmIvQKjDydU8= +github.com/multiformats/go-multiaddr v0.12.3/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII= 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= @@ -466,13 +363,11 @@ github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI1 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-multistream v0.5.0 h1:5htLSLl7lvJk3xx3qT/8Zm9J4K8vEOf/QGkvOGQAyiE= +github.com/multiformats/go-multistream v0.5.0/go.mod h1:n6tMZiwiP2wUsR8DgfDWw1dydlEqV3l6N3/GBsX6ILA= 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= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 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= @@ -484,13 +379,15 @@ github.com/olivere/elastic v6.2.14+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGe 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/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= 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/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= +github.com/opencontainers/runtime-spec v1.2.0/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= @@ -500,56 +397,35 @@ github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCr 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= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -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/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= 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/common v0.47.0 h1:p5Cz0FNHo7SnWOmWmoRozVcjEp0bIVU8cV7OShpjL1k= +github.com/prometheus/common v0.47.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= 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= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -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/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 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/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM= +github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= +github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= 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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 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= @@ -580,9 +456,6 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. 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= @@ -603,8 +476,8 @@ 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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -632,42 +505,35 @@ github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmv github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= +go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= +go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= 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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 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= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -675,51 +541,29 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh 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.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -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/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= 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= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -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/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= @@ -727,48 +571,23 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn 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= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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= @@ -776,79 +595,43 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -859,8 +642,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= @@ -869,51 +652,17 @@ golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -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= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -921,101 +670,38 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T 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= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -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= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -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= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -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= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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-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= @@ -1027,7 +713,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD 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= @@ -1040,17 +725,11 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd 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= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 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= +pgregory.net/rapid v0.6.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= 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= diff --git a/tools/x-repo-types/go.mod b/tools/x-repo-types/go.mod index df9ddf0114..34160acb0e 100644 --- a/tools/x-repo-types/go.mod +++ b/tools/x-repo-types/go.mod @@ -1,6 +1,8 @@ module github.com/algorand/go-algorand/tools/x-repo-types -go 1.20 +go 1.21 + +toolchain go1.21.10 replace github.com/algorand/go-algorand => ../.. From c4072a66b5bfc71058325242a3a5a13ccdc3c05d Mon Sep 17 00:00:00 2001 From: John Lee Date: Wed, 8 May 2024 14:01:54 -0400 Subject: [PATCH 119/218] Scripts: update buildnumber to use timezone-aware datetime (#5971) --- scripts/buildnumber.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/buildnumber.py b/scripts/buildnumber.py index 59de8a076b..7488572e0f 100755 --- a/scripts/buildnumber.py +++ b/scripts/buildnumber.py @@ -7,9 +7,9 @@ # e.g. if NOW is 5/28/2018 5:30am # => 305 -from datetime import datetime +from datetime import datetime, timezone -epoch = datetime(2018, 5, 25, 0, 0, 0) -d1 = datetime.utcnow() +epoch = datetime(2018, 5, 25, 0, 0, 0, tzinfo=timezone.utc) +d1 = datetime.now(timezone.utc) delta = d1 - epoch print("%d%02d" % (delta.days, d1.hour)) From 53a58cc6428ecd35d5b8058f768c505c2a0d01a8 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 9 May 2024 13:55:05 -0400 Subject: [PATCH 120/218] tests: fix TestExactAccountChunk (#5993) --- ledger/catchpointfilewriter_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ledger/catchpointfilewriter_test.go b/ledger/catchpointfilewriter_test.go index 2f53cc59b6..499adeedc8 100644 --- a/ledger/catchpointfilewriter_test.go +++ b/ledger/catchpointfilewriter_test.go @@ -29,6 +29,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/stretchr/testify/require" @@ -820,6 +821,33 @@ func TestExactAccountChunk(t *testing.T) { dl.fullBlock(&selfpay) } + // ensure both committed all pending changes before taking a catchpoint + // another approach is to modify the test and craft round numbers, + // and make the ledger to generate catchpoint itself when it is time + flushRound := func(l *Ledger) { + // Clear the timer to ensure a flush + l.trackers.mu.Lock() + l.trackers.lastFlushTime = time.Time{} + l.trackers.mu.Unlock() + + r, _ := l.LatestCommitted() + l.trackers.committedUpTo(r) + l.trackers.waitAccountsWriting() + } + flushRound(dl.generator) + flushRound(dl.validator) + + require.Eventually(t, func() bool { + dl.generator.accts.accountsMu.RLock() + dlg := len(dl.generator.accts.deltas) + dl.generator.accts.accountsMu.RUnlock() + + dl.validator.accts.accountsMu.RLock() + dlv := len(dl.validator.accts.deltas) + dl.validator.accts.accountsMu.RUnlock() + return dlg == dlv && dl.generator.Latest() == dl.validator.Latest() + }, 10*time.Second, 100*time.Millisecond) + tempDir := t.TempDir() catchpointDataFilePath := filepath.Join(tempDir, t.Name()+".data") From c7d281e44c1f02f0f570d1cc5275b6306ab39233 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 9 May 2024 14:30:52 -0400 Subject: [PATCH 121/218] tests: make e2e TestBasicSuspension less flaky (#5995) --- test/e2e-go/features/incentives/suspension_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/e2e-go/features/incentives/suspension_test.go b/test/e2e-go/features/incentives/suspension_test.go index 2b0f451c1b..6768f7926e 100644 --- a/test/e2e-go/features/incentives/suspension_test.go +++ b/test/e2e-go/features/incentives/suspension_test.go @@ -116,6 +116,12 @@ func TestBasicSuspension(t *testing.T) { fixture.SendMoneyAndWait(afterStop.LastRound+suspend20, 5, 1000, richAccount.Address, account10.Address, "") fixture.SendMoneyAndWait(afterStop.LastRound+suspend20, 5, 1000, richAccount.Address, account20.Address, "") + // make sure c10 node is in-sync with the network + status, err := fixture.LibGoalClient.Status() + a.NoError(err) + _, err = c10.WaitForRound(status.LastRound) + a.NoError(err) + // n20's account is now offline, but has voting key material (suspended) account, err = c10.AccountData(account20.Address) a.NoError(err) From 5ac3edcc613e01b0d6f86f008306d931dec08a00 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 9 May 2024 16:56:06 -0400 Subject: [PATCH 122/218] tests: make TestBasicPayouts less flaky (#5996) Co-authored-by: John Jannotti --- test/e2e-go/features/incentives/payouts_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/e2e-go/features/incentives/payouts_test.go b/test/e2e-go/features/incentives/payouts_test.go index 76a7caa004..1b9f4d0ec3 100644 --- a/test/e2e-go/features/incentives/payouts_test.go +++ b/test/e2e-go/features/incentives/payouts_test.go @@ -81,6 +81,9 @@ func TestBasicPayouts(t *testing.T) { a.NoError(err) burn, err := fixture.WaitForConfirmedTxn(uint64(txn.LastValid), txn.ID().String()) a.NoError(err) + // sync up with the network + _, err = c01.WaitForRound(*burn.ConfirmedRound) + a.NoError(err) data01, err = c01.AccountData(account01.Address) a.NoError(err) @@ -344,6 +347,9 @@ func rekeyreg(f *fixtures.RestClientFixture, a *require.Assertions, client libgo a.NoError(err) txn, err := f.WaitForConfirmedTxn(uint64(reReg.LastValid), onlineTxID) a.NoError(err) + // sync up with the network + _, err = client.WaitForRound(*txn.ConfirmedRound) + a.NoError(err) data, err = client.AccountData(address) a.NoError(err) a.Equal(basics.Online, data.Status) From a307b45e1027acf34fb47a39da4d1ad39b73e117 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Fri, 10 May 2024 14:01:42 +0000 Subject: [PATCH 123/218] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +0 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885 From 299b309a33c041493e43e3191a29d48db131500f Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 14 May 2024 13:16:20 -0400 Subject: [PATCH 124/218] logger: fix data race in tests (#5999) --- logging/log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging/log.go b/logging/log.go index d9b79eb9aa..48f83c1b9e 100644 --- a/logging/log.go +++ b/logging/log.go @@ -308,7 +308,7 @@ func (l logger) SetOutput(w io.Writer) { } func (l logger) setOutput(w io.Writer) { - l.entry.Logger.Out = w + l.entry.Logger.SetOutput(w) } func (l logger) getOutput() io.Writer { From a3efb562472105939b26d63dd8c00800a41ad27e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 14 May 2024 14:28:40 -0400 Subject: [PATCH 125/218] Typos: Correct some spelling mistakes (#6001) --- cmd/netgoal/network.go | 4 ++-- netdeploy/remote/deployedNetwork.go | 22 +++++++++---------- .../recipes/bootstrappedScenario/Makefile | 6 ++--- .../recipes/bootstrappedScenario/recipe.json | 12 +++++----- .../recipes/scenario1s/Makefile | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cmd/netgoal/network.go b/cmd/netgoal/network.go index 69fecbac26..d4ed3c277c 100644 --- a/cmd/netgoal/network.go +++ b/cmd/netgoal/network.go @@ -168,9 +168,9 @@ func runBuildNetwork() error { return fmt.Errorf("error resolving bootstrap file: %v", err) } net.BootstrappedNet = fileTemplate - net.SetUseBoostrappedFiles(bootstrapLoadingFile) + net.SetUseBootstrappedFiles(bootstrapLoadingFile) } else { - net.SetUseBoostrappedFiles(false) + net.SetUseBootstrappedFiles(false) } net.SetUseExistingGenesisFiles(networkUseGenesisFiles) diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go index 729409fe94..25de422026 100644 --- a/netdeploy/remote/deployedNetwork.go +++ b/netdeploy/remote/deployedNetwork.go @@ -75,12 +75,12 @@ type DeployedNetworkConfig struct { // DeployedNetwork represents the complete configuration specification for a deployed network type DeployedNetwork struct { - useExistingGenesis bool - createBoostrappedNetwork bool - GenesisData gen.GenesisData - Topology topology - Hosts []HostConfig - BootstrappedNet BootstrappedNetwork + useExistingGenesis bool + createBootstrappedNetwork bool + GenesisData gen.GenesisData + Topology topology + Hosts []HostConfig + BootstrappedNet BootstrappedNetwork } type netState struct { @@ -253,12 +253,12 @@ func (cfg *DeployedNetwork) SetUseExistingGenesisFiles(useExisting bool) bool { return old } -// SetUseBoostrappedFiles sets the override flag indicating we should use existing genesis +// SetUseBootstrappedFiles sets the override flag indicating we should use existing genesis // files instead of generating new ones. This is useful for permanent networks like devnet and testnet. // Returns the previous value. -func (cfg *DeployedNetwork) SetUseBoostrappedFiles(boostrappedFile bool) bool { - old := cfg.createBoostrappedNetwork - cfg.createBoostrappedNetwork = boostrappedFile +func (cfg *DeployedNetwork) SetUseBootstrappedFiles(bootstrappedFile bool) bool { + old := cfg.createBootstrappedNetwork + cfg.createBootstrappedNetwork = bootstrappedFile return old } @@ -346,7 +346,7 @@ func (cfg DeployedNetwork) BuildNetworkFromTemplate(buildCfg BuildConfig, rootDi return } - if cfg.createBoostrappedNetwork { + if cfg.createBootstrappedNetwork { fmt.Println("Generating db files ") cfg.GenerateDatabaseFiles(cfg.BootstrappedNet, genesisFolder) diff --git a/test/testdata/deployednettemplates/recipes/bootstrappedScenario/Makefile b/test/testdata/deployednettemplates/recipes/bootstrappedScenario/Makefile index f5db1ebbc1..4611e30ed2 100644 --- a/test/testdata/deployednettemplates/recipes/bootstrappedScenario/Makefile +++ b/test/testdata/deployednettemplates/recipes/bootstrappedScenario/Makefile @@ -2,7 +2,7 @@ PARAMS=-w 20 -R 8 -N 20 -n 20 --npn-algod-nodes 10 --node-template node.json --relay-template relay.json --non-participating-node-template nonPartNode.json --last-part-key-round 50000 FILEPARAMS=--rounds 1600 --ntxns 20000 --naccounts 30000000 --nassets 20000 --napps 20000 --wallet-name "wallet1" --bal 50000000 --bal 50000001 --deterministic -all: net.json genesis.json topology.json boostrappedFile.json +all: net.json genesis.json topology.json bootstrappedFile.json net.json: node.json nonPartNode.json ${GOPATH}/bin/netgoal Makefile netgoal generate -t net -r /tmp/wat -o net.json ${PARAMS} @@ -15,8 +15,8 @@ genesis.json: ${GOPATH}/bin/netgoal Makefile topology.json: gen_topology.py python3 gen_topology.py -boostrappedFile.json: ${GOPATH}/bin/netgoal Makefile - netgoal generate -t loadingFile -r /tmp/wat -o boostrappedFile.json ${FILEPARAMS} +bootstrappedFile.json: ${GOPATH}/bin/netgoal Makefile + netgoal generate -t loadingFile -r /tmp/wat -o bootstrappedFile.json ${FILEPARAMS} clean: rm -f net.json genesis.json diff --git a/test/testdata/deployednettemplates/recipes/bootstrappedScenario/recipe.json b/test/testdata/deployednettemplates/recipes/bootstrappedScenario/recipe.json index 766328dbb3..55805a6eb9 100644 --- a/test/testdata/deployednettemplates/recipes/bootstrappedScenario/recipe.json +++ b/test/testdata/deployednettemplates/recipes/bootstrappedScenario/recipe.json @@ -1,8 +1,8 @@ { - "GenesisFile":"genesis.json", - "NetworkFile":"net.json", - "ConfigFile": "../../configs/reference.json", - "HostTemplatesFile": "../../hosttemplates/hosttemplates.json", - "TopologyFile": "topology.json", - "BootstrappedFile": "boostrappedFile.json" + "GenesisFile": "genesis.json", + "NetworkFile": "net.json", + "ConfigFile": "../../configs/reference.json", + "HostTemplatesFile": "../../hosttemplates/hosttemplates.json", + "TopologyFile": "topology.json", + "BootstrappedFile": "bootstrappedFile.json" } diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/Makefile b/test/testdata/deployednettemplates/recipes/scenario1s/Makefile index f4b1910906..ed8a70132e 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/Makefile +++ b/test/testdata/deployednettemplates/recipes/scenario1s/Makefile @@ -1,7 +1,7 @@ # scenario1s is scenario1 but smaller, (100 nodes, 100 wallets) -> (20 nodes, 20 wallets), each algod gets single tenancy on a smaller ec2 instance PARAMS=-w 20 -R 8 -N 20 -n 20 --npn-algod-nodes 10 --node-template node.json --relay-template relay.json --non-participating-node-template nonPartNode.json -all: net.json genesis.json topology.json boostrappedFile.json +all: net.json genesis.json topology.json bootstrappedFile.json net.json: node.json nonPartNode.json ${GOPATH}/bin/netgoal Makefile netgoal generate -t net -r /tmp/wat -o net.json ${PARAMS} From 98fdd2a096f452bebf06eaa676f3bb142bf21b0f Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 15 May 2024 13:49:06 -0400 Subject: [PATCH 126/218] AVM: incentive opcodes (#5984) Co-authored-by: Jason Paulos Co-authored-by: cce <51567+cce@users.noreply.github.com> --- agreement/proposal.go | 2 +- agreement/pseudonode.go | 2 +- agreement/pseudonode_test.go | 2 +- agreement/selector.go | 7 +- cmd/tealdbg/localLedger.go | 28 + daemon/algod/api/server/v2/dryrun.go | 26 + data/transactions/logic/assembler_test.go | 11 +- data/transactions/logic/doc.go | 4 +- data/transactions/logic/eval.go | 57 +- data/transactions/logic/evalStateful_test.go | 54 + data/transactions/logic/eval_test.go | 13 +- data/transactions/logic/fields.go | 93 +- data/transactions/logic/fields_string.go | 30 +- data/transactions/logic/fields_test.go | 31 +- data/transactions/logic/ledger_test.go | 31 + data/transactions/logic/opcodes.go | 2 + data/transactions/logic/teal.tmLanguage.json | 2 +- ledger/apptxn_test.go | 1029 ++++++++--------- ledger/double_test.go | 13 +- ledger/eval/appcow_test.go | 8 + ledger/eval/applications.go | 17 + ledger/eval/cow.go | 10 + ledger/eval/cow_test.go | 29 + ledger/eval/eval.go | 63 + ledger/eval/eval_test.go | 80 +- .../prefetcher/prefetcher_alignment_test.go | 11 + ledger/eval_simple_test.go | 294 ++++- ledger/evalindexer.go | 250 ---- ledger/evalindexer_test.go | 396 ------- ledger/tracker.go | 8 + 30 files changed, 1282 insertions(+), 1321 deletions(-) delete mode 100644 ledger/evalindexer.go delete mode 100644 ledger/evalindexer_test.go diff --git a/agreement/proposal.go b/agreement/proposal.go index e696bfeb4b..e8d506c4b5 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -277,7 +277,7 @@ func verifyProposer(p unauthenticatedProposal, ledger LedgerReader) error { // the header lacks it, the returned balanceRecord will be the right record. func payoutEligible(rnd basics.Round, proposer basics.Address, ledger LedgerReader, cparams config.ConsensusParams) (bool, basics.OnlineAccountData, error) { // Check the balance from the agreement round - balanceRound := balanceRound(rnd, cparams) + balanceRound := BalanceRound(rnd, cparams) balanceRecord, err := ledger.LookupAgreement(balanceRound, proposer) if err != nil { return false, basics.OnlineAccountData{}, err diff --git a/agreement/pseudonode.go b/agreement/pseudonode.go index fe5423c025..2940a826f0 100644 --- a/agreement/pseudonode.go +++ b/agreement/pseudonode.go @@ -220,7 +220,7 @@ func (n *asyncPseudonode) loadRoundParticipationKeys(voteRound basics.Round) []a n.participationKeys = nil return nil } - balanceRound := balanceRound(voteRound, cparams) + balanceRound := BalanceRound(voteRound, cparams) // measure the time it takes to acquire the voting keys. beforeVotingKeysTime := time.Now() diff --git a/agreement/pseudonode_test.go b/agreement/pseudonode_test.go index ee83061ba0..fdc92f1805 100644 --- a/agreement/pseudonode_test.go +++ b/agreement/pseudonode_test.go @@ -449,7 +449,7 @@ func TestPseudonodeLoadingOfParticipationKeys(t *testing.T) { for rnd := basics.Round(3); rnd < 1000; rnd += 43 { keyManagerProxy.target = func(votingRound, balanceRnd basics.Round) []account.ParticipationRecordForRound { require.Equal(t, rnd, votingRound) - require.Equal(t, balanceRound(rnd, cparams), balanceRnd) + require.Equal(t, BalanceRound(rnd, cparams), balanceRnd) return keyManager.VotingKeys(votingRound, balanceRnd) } pb.loadRoundParticipationKeys(basics.Round(rnd)) diff --git a/agreement/selector.go b/agreement/selector.go index 76f9d5a312..2d0f980ac3 100644 --- a/agreement/selector.go +++ b/agreement/selector.go @@ -47,7 +47,10 @@ func (sel selector) CommitteeSize(proto config.ConsensusParams) uint64 { return sel.Step.committeeSize(proto) } -func balanceRound(r basics.Round, cparams config.ConsensusParams) basics.Round { +// BalanceRound returns the round that should be considered by agreement when +// looking at online stake (and status and key material). It is exported so that +// AVM can provide opcodes that return the same data. +func BalanceRound(r basics.Round, cparams config.ConsensusParams) basics.Round { return r.SubSaturate(basics.Round(2 * cparams.SeedRefreshInterval * cparams.SeedLookback)) } @@ -61,7 +64,7 @@ func membership(l LedgerReader, addr basics.Address, r basics.Round, p period, s if err != nil { return } - balanceRound := balanceRound(r, cparams) + balanceRound := BalanceRound(r, cparams) seedRound := seedRound(r, cparams) record, err := l.LookupAgreement(balanceRound, addr) diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go index d97c8df70f..d495fbb328 100644 --- a/cmd/tealdbg/localLedger.go +++ b/cmd/tealdbg/localLedger.go @@ -336,6 +336,34 @@ func (l *localLedger) LookupWithoutRewards(rnd basics.Round, addr basics.Address return ledgercore.ToAccountData(ad), rnd, nil } +func (l *localLedger) LookupAgreement(rnd basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { + // tealdbg does not understand rewards, so no pending rewards are applied. + // Further, it has no history, so we return the _current_ information, + // ignoring the `rnd` argument. + ad := l.balances[addr] + if ad.Status != basics.Online { + return basics.OnlineAccountData{}, nil + } + + return basics.OnlineAccountData{ + MicroAlgosWithRewards: ad.MicroAlgos, + VotingData: basics.VotingData{ + VoteID: ad.VoteID, + SelectionID: ad.SelectionID, + StateProofID: ad.StateProofID, + VoteFirstValid: ad.VoteFirstValid, + VoteLastValid: ad.VoteLastValid, + VoteKeyDilution: ad.VoteKeyDilution, + }, + IncentiveEligible: ad.IncentiveEligible, + }, nil +} + +func (l *localLedger) OnlineCirculation(rnd basics.Round, voteRound basics.Round) (basics.MicroAlgos, error) { + // A constant is fine for tealdbg + return basics.Algos(1_000_000_000), nil // 1B +} + func (l *localLedger) GetCreatorForRound(rnd basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { switch ctype { case basics.AssetCreatable: diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 9c3355cd10..d3924eaf1d 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -308,6 +308,32 @@ func (dl *dryrunLedger) LookupWithoutRewards(rnd basics.Round, addr basics.Addre return ledgercore.ToAccountData(ad), rnd, nil } +func (dl *dryrunLedger) LookupAgreement(rnd basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { + // dryrun does not understand rewards, so we build the result without adding pending rewards. + // we also have no history, so we return current values + ad, _, err := dl.lookup(rnd, addr) + if err != nil || ad.Status != basics.Online { + return basics.OnlineAccountData{}, err + } + return basics.OnlineAccountData{ + MicroAlgosWithRewards: ad.MicroAlgos, + VotingData: basics.VotingData{ + VoteID: ad.VoteID, + SelectionID: ad.SelectionID, + StateProofID: ad.StateProofID, + VoteFirstValid: ad.VoteFirstValid, + VoteLastValid: ad.VoteLastValid, + VoteKeyDilution: ad.VoteKeyDilution, + }, + IncentiveEligible: ad.IncentiveEligible, + }, nil +} + +func (dl *dryrunLedger) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { + // dryrun doesn't support setting the global online stake, so we'll just return a constant + return basics.Algos(1_000_000_000), nil // 1B +} + func (dl *dryrunLedger) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { ad, _, err := dl.lookup(rnd, addr) if err != nil { diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index ec5537a701..79b37186df 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -427,6 +427,11 @@ match_label1: pushbytess "1" "2" "1" ` +const incentiveNonsense = ` +online_stake +voter_params_get VoterIncentiveEligible +` + const stateProofNonsense = ` pushbytes 0x0123456789abcd sumhash512 @@ -445,7 +450,7 @@ const spliceNonsence = ` const v10Nonsense = v9Nonsense + pairingNonsense + spliceNonsence -const v11Nonsense = v10Nonsense + stateProofNonsense +const v11Nonsense = v10Nonsense + incentiveNonsense + stateProofNonsense const v6Compiled = "2004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f2310231123122313231418191a1b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b400b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a" @@ -467,9 +472,11 @@ const spliceCompiled = "d2d3" const v10Compiled = v9Compiled + pairingCompiled + spliceCompiled +const incentiveCompiled = "757401" + const stateProofCompiled = "80070123456789abcd86494985" -const V11Compiled = v10Compiled + stateProofCompiled +const V11Compiled = v10Compiled + incentiveCompiled + stateProofCompiled var nonsense = map[uint64]string{ 1: v1Nonsense, diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index fde870a9d4..3bab156561 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -250,6 +250,8 @@ var opDescByName = map[string]OpDesc{ "asset_params_get": {"X is field F from asset A. Y is 1 if A exists, else 0", "params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: did_exist flag (1 if the asset existed and 0 otherwise), value.", []string{"asset params field index"}}, "app_params_get": {"X is field F from app A. Y is 1 if A exists, else 0", "params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag (1 if the application existed and 0 otherwise), value.", []string{"app params field index"}}, "acct_params_get": {"X is field F from account A. Y is 1 if A owns positive algos, else 0", "", []string{"account params field index"}}, + "voter_params_get": {"X is field F from online account A as of the balance round: 320 rounds before the current round. Y is 1 if A had positive algos online in the agreement round, else Y is 0 and X is a type specific zero-value", "", []string{"voter params field index"}}, + "online_stake": {"the total online stake in the agreement round", "", nil}, "assert": {"immediately fail unless A is a non-zero number", "", nil}, "callsub": {"branch unconditionally to TARGET, saving the next instruction on the call stack", "The call stack is separate from the data stack. Only `callsub`, `retsub`, and `proto` manipulate it.", []string{"branch offset"}}, "proto": {"Prepare top call frame for a retsub that will assume A args and R return values.", "Fails unless the last instruction executed was a `callsub`.", []string{"number of arguments", "number of return values"}}, @@ -355,7 +357,7 @@ var OpGroups = map[string][]string{ "Cryptography": {"sha256", "keccak256", "sha512_256", "sha3_256", "sumhash512", "falcon_verify", "ed25519verify", "ed25519verify_bare", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "vrf_verify", "ec_add", "ec_scalar_mul", "ec_pairing_check", "ec_multi_scalar_mul", "ec_subgroup_check", "ec_map_to"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "pushints", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "pushbytess", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "popn", "dup", "dup2", "dupn", "dig", "bury", "cover", "uncover", "frame_dig", "frame_bury", "swap", "select", "assert", "callsub", "proto", "retsub", "switch", "match"}, - "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log", "block"}, + "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "voter_params_get", "online_stake", "log", "block"}, "Box Access": {"box_create", "box_extract", "box_replace", "box_splice", "box_del", "box_len", "box_get", "box_put", "box_resize"}, "Inner Transactions": {"itxn_begin", "itxn_next", "itxn_field", "itxn_submit", "itxn", "itxna", "itxnas", "gitxn", "gitxna", "gitxnas"}, } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 3008250a54..36ba3bb788 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -248,6 +248,11 @@ type LedgerForLogic interface { Round() basics.Round PrevTimestamp() int64 + // These are simplifications of the underlying Ledger methods that take a + // round argument. They implicitly use agreement's BalanceRound (320 back). + AgreementData(addr basics.Address) (basics.OnlineAccountData, error) + OnlineStake() (basics.MicroAlgos, error) + AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error) AssetParams(aidx basics.AssetIndex) (basics.AssetParams, basics.Address, error) AppParams(aidx basics.AppIndex) (basics.AppParams, basics.Address, error) @@ -3736,7 +3741,7 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er return sv, fmt.Errorf("invalid global field %s", fs.field) } - if fs.ftype.AVMType != sv.avmType() { + if err == nil && fs.ftype.AVMType != sv.avmType() { return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.avmType()) } @@ -5019,12 +5024,62 @@ func opAcctParamsGet(cx *EvalContext) error { value.Uint = account.TotalBoxes case AcctTotalBoxBytes: value.Uint = account.TotalBoxBytes + case AcctIncentiveEligible: + value = boolToSV(account.IncentiveEligible) + case AcctLastHeartbeat: + value.Uint = uint64(account.LastHeartbeat) + case AcctLastProposed: + value.Uint = uint64(account.LastProposed) + default: + return fmt.Errorf("invalid account field %s", fs.field) } cx.Stack[last] = value cx.Stack = append(cx.Stack, boolToSV(account.MicroAlgos.Raw > 0)) return nil } +func opVoterParamsGet(cx *EvalContext) error { + last := len(cx.Stack) - 1 // acct + addr, _, err := cx.accountReference(cx.Stack[last]) + if err != nil { + return err + } + + paramField := VoterParamsField(cx.program[cx.pc+1]) + fs, ok := voterParamsFieldSpecByField(paramField) + if !ok || fs.version > cx.version { + return fmt.Errorf("invalid voter_params_get field %d", paramField) + } + + account, err := cx.Ledger.AgreementData(addr) + if err != nil { + return err + } + + var value stackValue + + switch fs.field { + case VoterBalance: + value.Uint = account.MicroAlgosWithRewards.Raw + case VoterIncentiveEligible: + value = boolToSV(account.IncentiveEligible) + default: + return fmt.Errorf("invalid voter field %s", fs.field) + } + cx.Stack[last] = value + cx.Stack = append(cx.Stack, boolToSV(account.MicroAlgosWithRewards.Raw > 0)) + return nil +} + +func opOnlineStake(cx *EvalContext) error { + amount, err := cx.Ledger.OnlineStake() + if err != nil { + return err + } + cx.Stack = append(cx.Stack, stackValue{Uint: amount.Raw}) + return nil +} + func opLog(cx *EvalContext) error { last := len(cx.Stack) - 1 diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 91ad91d1fe..abff0ddb83 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -348,6 +348,8 @@ log "int 0; int 0; app_params_get AppApprovalProgram": 5, "byte 0x01; log": 5, sender + "acct_params_get AcctBalance": 7, + sender + "voter_params_get VoterBalance": 11, + "online_stake": 11, "byte 0x1234; int 12; box_create": 8, "byte 0x1234; int 12; int 4; box_extract": 8, @@ -487,6 +489,9 @@ func testApp(t *testing.T, program string, ep *EvalParams, problems ...string) ( return testAppBytes(t, ops.Program, ep, problems...) } +// testAppCreator is the creator of the 888 app that is inserted when testing an app call +const testAppCreator = "47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU" + func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) (transactions.EvalDelta, error) { t.Helper() if ep == nil { @@ -497,6 +502,12 @@ func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...stri aid := ep.TxnGroup[0].Txn.ApplicationID if aid == 0 { aid = basics.AppIndex(888) + // we're testing an app call without the caller specifying details about + // the app, so conjure up boring app params to make the `global + // AppCreator` work. + addr, err := basics.UnmarshalChecksumAddress(testAppCreator) + require.NoError(t, err) + ep.Ledger.(*Ledger).NewApp(addr, 888, basics.AppParams{}) } return testAppFull(t, program, 0, aid, ep, problems...) } @@ -1598,6 +1609,49 @@ func TestAcctParams(t *testing.T) { ledger.NewAsset(tx.Sender, 3000, basics.AssetParams{}) test("txn Sender; acct_params_get AcctTotalAssetsCreated; assert; int 1; ==") test("txn Sender; acct_params_get AcctTotalAssets; assert; int 1; ==") + + if ep.Proto.LogicSigVersion < 11 { + return // the rest uses fields that came at 11 + } + test("txn Sender; acct_params_get AcctIncentiveEligible; assert; !") + test("txn Sender; acct_params_get AcctLastHeartbeat; assert; !") + test("txn Sender; acct_params_get AcctLastProposed; assert; !") + }) +} + +func TestVoterParams(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // start at 11 for acct_params_get + testLogicRange(t, 11, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + test := func(source string) { + t.Helper() + testApp(t, source, ep) + } + + test("txn Sender; voter_params_get VoterBalance; !; assert; int 0; ==") + test("txn Sender; voter_params_get VoterIncentiveEligible; !; assert; int 0; ==") + + // The logic package test ledger just returns current values + ledger.NewAccount(tx.Sender, 42) + test("txn Sender; voter_params_get VoterBalance; assert; int 42; ==") + test("txn Sender; voter_params_get VoterIncentiveEligible; assert; !") + }) +} + +func TestOnlineStake(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // start at 11 for online_stake + testLogicRange(t, 11, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + test := func(source string) { + t.Helper() + testApp(t, source, ep) + } + + test("online_stake; int 3333000000; ==") // test ledger hard codes 3333 algos }) } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 3ede2c134b..3a81a9cbef 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1160,8 +1160,6 @@ int 1 && ` -const testAddr = "47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU" - const globalV2TestProgram = globalV1TestProgram + ` global LogicSigVersion int 1 @@ -1182,7 +1180,7 @@ int 888 ` const globalV3TestProgram = globalV2TestProgram + ` global CreatorAddress -addr ` + testAddr + ` +addr ` + testAppCreator + ` == && ` @@ -1239,10 +1237,10 @@ const globalV11TestProgram = globalV10TestProgram + ` // No new globals in v11 ` -func TestGlobal(t *testing.T) { +func TestAllGlobals(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() + type desc struct { lastField GlobalField program string @@ -1270,10 +1268,6 @@ func TestGlobal(t *testing.T) { require.Equal(t, tests[AssemblerMaxVersion].lastField, invalidGlobalField-1, "did you add a new global field?") - ledger := NewLedger(nil) - addr, err := basics.UnmarshalChecksumAddress(testAddr) - require.NoError(t, err) - ledger.NewApp(addr, 888, basics.AppParams{}) for v := uint64(1); v <= AssemblerMaxVersion; v++ { _, ok := tests[v] require.True(t, ok) @@ -1294,7 +1288,6 @@ func TestGlobal(t *testing.T) { appcall.Txn.Group = crypto.Digest{0x07, 0x06} ep := defaultAppParams(appcall) - ep.Ledger = ledger testApp(t, tests[v].program, ep) }) } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index d0603d0ffc..367967089b 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -23,7 +23,7 @@ import ( "github.com/algorand/go-algorand/protocol" ) -//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AcctParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,EcGroup,Base64Encoding,JSONRefType,VrfStandard,BlockField -output=fields_string.go +//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AcctParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,EcGroup,Base64Encoding,JSONRefType,VoterParamsField,VrfStandard,BlockField -output=fields_string.go // FieldSpec unifies the various specs for assembly, disassembly, and doc generation. type FieldSpec interface { @@ -1319,6 +1319,14 @@ const ( // AcctTotalBoxBytes is the number of bytes in all boxes of this app account AcctTotalBoxBytes + // AcctIncentiveEligible is whether this account opted into block payouts by + // paying extra in `keyreg`. Does not reflect eligibility based on balance. + AcctIncentiveEligible + // AcctLastProposed is the last time this account proposed. Does not include _this_ round. + AcctLastProposed + // AcctLastHeartbeat is the last heartbeat from this account. + AcctLastHeartbeat + // AcctTotalAppSchema - consider how to expose invalidAcctParamsField // compile-time constant for number of fields @@ -1363,6 +1371,10 @@ var acctParamsFieldSpecs = [...]acctParamsFieldSpec{ {AcctTotalAssets, StackUint64, 8, "The numbers of ASAs held by this account (including ASAs this account created)."}, {AcctTotalBoxes, StackUint64, boxVersion, "The number of existing boxes created by this account's app."}, {AcctTotalBoxBytes, StackUint64, boxVersion, "The total number of bytes used by this account's app's box keys and values."}, + + {AcctIncentiveEligible, StackBoolean, incentiveVersion, "Has this account opted into block payouts"}, + {AcctLastProposed, StackUint64, incentiveVersion, "The round number of the last block this account proposed."}, + {AcctLastHeartbeat, StackUint64, incentiveVersion, "The round number of the last block this account sent a heartbeat."}, } func acctParamsFieldSpecByField(f AcctParamsField) (acctParamsFieldSpec, bool) { @@ -1388,6 +1400,78 @@ var AcctParamsFields = FieldGroup{ acctParamsFieldSpecByName, } +// VoterParamsField is an enum for `voter_params_get` opcode +type VoterParamsField int + +const ( + // VoterBalance is the balance, with pending rewards, from the balance + // round. It is 0 if the account was offline then. + VoterBalance VoterParamsField = iota + + // expose voter keys? + + // VoterIncentiveEligible is whether this account opted into block payouts + // by paying extra in `keyreg`. Does not reflect eligibility based on + // balance. The value is returned for the balance round and is _false_ if + // the account was offline then. + VoterIncentiveEligible + + invalidVoterParamsField // compile-time constant for number of fields +) + +var voterParamsFieldNames [invalidVoterParamsField]string + +type voterParamsFieldSpec struct { + field VoterParamsField + ftype StackType + version uint64 + doc string +} + +func (fs voterParamsFieldSpec) Field() byte { + return byte(fs.field) +} +func (fs voterParamsFieldSpec) Type() StackType { + return fs.ftype +} +func (fs voterParamsFieldSpec) OpVersion() uint64 { + return incentiveVersion +} +func (fs voterParamsFieldSpec) Version() uint64 { + return fs.version +} +func (fs voterParamsFieldSpec) Note() string { + return fs.doc +} + +var voterParamsFieldSpecs = [...]voterParamsFieldSpec{ + {VoterBalance, StackUint64, 6, "Online stake in microalgos"}, + {VoterIncentiveEligible, StackBoolean, incentiveVersion, "Had this account opted into block payouts"}, +} + +func voterParamsFieldSpecByField(f VoterParamsField) (voterParamsFieldSpec, bool) { + if int(f) >= len(voterParamsFieldSpecs) { + return voterParamsFieldSpec{}, false + } + return voterParamsFieldSpecs[f], true +} + +var voterParamsFieldSpecByName = make(voterNameSpecMap, len(voterParamsFieldNames)) + +type voterNameSpecMap map[string]voterParamsFieldSpec + +func (s voterNameSpecMap) get(name string) (FieldSpec, bool) { + fs, ok := s[name] + return fs, ok +} + +// VoterParamsFields describes voter_params_get's immediates +var VoterParamsFields = FieldGroup{ + "voter_params", "Fields", + voterParamsFieldNames[:], + voterParamsFieldSpecByName, +} + func init() { equal := func(x int, y int) { if x != y { @@ -1479,6 +1563,13 @@ func init() { acctParamsFieldSpecByName[s.field.String()] = s } + equal(len(voterParamsFieldSpecs), len(voterParamsFieldNames)) + for i, s := range voterParamsFieldSpecs { + equal(int(s.field), i) + voterParamsFieldNames[i] = s.field.String() + voterParamsFieldSpecByName[s.field.String()] = s + } + txnTypeMap = make(map[string]uint64) for i, tt := range TxnTypeNames { txnTypeMap[tt] = uint64(i) diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 5b92357909..d925e69ee1 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AcctParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,EcGroup,Base64Encoding,JSONRefType,VrfStandard,BlockField -output=fields_string.go"; DO NOT EDIT. +// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AcctParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,EcGroup,Base64Encoding,JSONRefType,VoterParamsField,VrfStandard,BlockField -output=fields_string.go"; DO NOT EDIT. package logic @@ -195,12 +195,15 @@ func _() { _ = x[AcctTotalAssets-9] _ = x[AcctTotalBoxes-10] _ = x[AcctTotalBoxBytes-11] - _ = x[invalidAcctParamsField-12] + _ = x[AcctIncentiveEligible-12] + _ = x[AcctLastProposed-13] + _ = x[AcctLastHeartbeat-14] + _ = x[invalidAcctParamsField-15] } -const _AcctParamsField_name = "AcctBalanceAcctMinBalanceAcctAuthAddrAcctTotalNumUintAcctTotalNumByteSliceAcctTotalExtraAppPagesAcctTotalAppsCreatedAcctTotalAppsOptedInAcctTotalAssetsCreatedAcctTotalAssetsAcctTotalBoxesAcctTotalBoxBytesinvalidAcctParamsField" +const _AcctParamsField_name = "AcctBalanceAcctMinBalanceAcctAuthAddrAcctTotalNumUintAcctTotalNumByteSliceAcctTotalExtraAppPagesAcctTotalAppsCreatedAcctTotalAppsOptedInAcctTotalAssetsCreatedAcctTotalAssetsAcctTotalBoxesAcctTotalBoxBytesAcctIncentiveEligibleAcctLastProposedAcctLastHeartbeatinvalidAcctParamsField" -var _AcctParamsField_index = [...]uint8{0, 11, 25, 37, 53, 74, 96, 116, 136, 158, 173, 187, 204, 226} +var _AcctParamsField_index = [...]uint16{0, 11, 25, 37, 53, 74, 96, 116, 136, 158, 173, 187, 204, 225, 241, 258, 280} func (i AcctParamsField) String() string { if i < 0 || i >= AcctParamsField(len(_AcctParamsField_index)-1) { @@ -328,6 +331,25 @@ func (i JSONRefType) String() string { } return _JSONRefType_name[_JSONRefType_index[i]:_JSONRefType_index[i+1]] } +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[VoterBalance-0] + _ = x[VoterIncentiveEligible-1] + _ = x[invalidVoterParamsField-2] +} + +const _VoterParamsField_name = "VoterBalanceVoterIncentiveEligibleinvalidVoterParamsField" + +var _VoterParamsField_index = [...]uint8{0, 12, 34, 57} + +func (i VoterParamsField) String() string { + if i < 0 || i >= VoterParamsField(len(_VoterParamsField_index)-1) { + return "VoterParamsField(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _VoterParamsField_name[_VoterParamsField_index[i]:_VoterParamsField_index[i+1]] +} func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 7cec0b936f..ab2548c306 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -29,7 +29,8 @@ import ( // ensure v2+ fields fail in TEAL assembler and evaluator on a version before they introduced // ensure v2+ fields error in v1 program -func TestGlobalFieldsVersions(t *testing.T) { +// ensure the types of the returned values are correct +func TestGlobalVersionsAndTypes(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -42,7 +43,7 @@ func TestGlobalFieldsVersions(t *testing.T) { require.Greater(t, len(fields), 1) for _, field := range fields { - text := fmt.Sprintf("global %s", field.field.String()) + text := "global " + field.field.String() // check assembler fails if version before introduction testLine(t, text, assemblerNoVersion, "...was introduced in...") for v := uint64(0); v < field.version; v++ { @@ -50,6 +51,32 @@ func TestGlobalFieldsVersions(t *testing.T) { } testLine(t, text, field.version, "") + // tack on a type check, and return a value (`int` gets compiled + // differently in different versions, so use intc_0 explicitly + switch field.ftype.AVMType { + case avmUint64: // ensure the return type is uint64 by using ! + text = "intcblock 1;" + text + "; !; pop; intc_0" + case avmBytes: // ensure the return type is bytes by using len + text = "intcblock 1;" + text + "; len; pop; intc_0" + case avmAny: + text = "intcblock 1;" + text + "; pop; intc_0" + } + + // check success in AssemblerMaxVersion and fs.version + for _, ver := range []uint64{AssemblerMaxVersion, field.version} { + ops := testProg(t, text, ver) + switch field.mode { + case ModeSig: + testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(ver)) + case ModeApp: + testAppBytes(t, ops.Program, defaultAppParamsWithVersion(ver)) + case modeAny: + testLogicBytes(t, ops.Program, defaultSigParamsWithVersion(ver)) + testAppBytes(t, ops.Program, defaultAppParamsWithVersion(ver)) + default: + t.Fail() + } + } ops := testProg(t, text, AssemblerMaxVersion) // check on a version before the field version diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 0254a44678..3dcead5e51 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -216,6 +216,12 @@ func (l *Ledger) PrevTimestamp() int64 { return int64(rand.Uint32() + 1) } +// OnlineStake returns the online stake that applies to the latest round (so +// it's actually the online stake from 320 rounds ago) +func (l *Ledger) OnlineStake() (basics.MicroAlgos, error) { + return basics.Algos(3333), nil +} + // BlockHdr returns the block header for the given round, if it is available func (l *Ledger) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) { hdr := bookkeeping.BlockHeader{} @@ -310,6 +316,31 @@ func (l *Ledger) AccountData(addr basics.Address) (ledgercore.AccountData, error }, nil } +// AgreementData is not a very high-fidelity fake. There's no time delay, it +// just returns the data that's in AccountData, reshaped into an +// OnlineAccountData. +func (l *Ledger) AgreementData(addr basics.Address) (basics.OnlineAccountData, error) { + ad, err := l.AccountData(addr) + if err != nil { + return basics.OnlineAccountData{}, err + } + // You might imagine this conversion function exists. It does, but requires + // rewards handling because OnlineAccountData should have rewards + // paid. Here, we ignore that for simple tests. + return basics.OnlineAccountData{ + MicroAlgosWithRewards: ad.MicroAlgos, + VotingData: basics.VotingData{ + VoteID: ad.VoteID, + SelectionID: ad.SelectionID, + StateProofID: ad.StateProofID, + VoteFirstValid: ad.VoteFirstValid, + VoteLastValid: ad.VoteLastValid, + VoteKeyDilution: ad.VoteKeyDilution, + }, + IncentiveEligible: ad.IncentiveEligible, + }, nil +} + // Authorizer returns the address that must authorize txns from a // given address. It's either the address itself, or the value it has // been rekeyed to. diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index bb5992ae44..f3f8bfe37d 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -641,6 +641,8 @@ var OpSpecs = []OpSpec{ {0x71, "asset_params_get", opAssetParamsGet, proto("i:aT"), 2, field("f", &AssetParamsFields).only(ModeApp)}, {0x72, "app_params_get", opAppParamsGet, proto("i:aT"), 5, field("f", &AppParamsFields).only(ModeApp)}, {0x73, "acct_params_get", opAcctParamsGet, proto("a:aT"), 6, field("f", &AcctParamsFields).only(ModeApp)}, + {0x74, "voter_params_get", opVoterParamsGet, proto("a:aT"), incentiveVersion, field("f", &VoterParamsFields).only(ModeApp)}, + {0x75, "online_stake", opOnlineStake, proto(":i"), incentiveVersion, only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("i:i"), 3, only(ModeApp)}, {0x78, "min_balance", opMinBalance, proto("a:i"), directRefEnabledVersion, only(ModeApp)}, diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 915957f7f0..53984e8dd2 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -72,7 +72,7 @@ }, { "name": "keyword.other.unit.teal", - "match": "^(box_create|box_del|box_extract|box_get|box_len|box_put|box_replace|box_resize|box_splice|acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|block|log|min_balance)\\b" + "match": "^(box_create|box_del|box_extract|box_get|box_len|box_put|box_replace|box_resize|box_splice|acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|block|log|min_balance|online_stake|voter_params_get)\\b" }, { "name": "keyword.operator.teal", diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 8e40d327e9..a7b3b15214 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -1104,13 +1104,16 @@ func TestKeyreg(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - app := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 31 allowed inner keyreg + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + app := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` txn ApplicationArgs 0 byte "pay" == @@ -1133,71 +1136,59 @@ nonpart: itxn_field Nonparticipation itxn_submit `), - } + } - // Create the app - eval := nextBlock(t, l) - txns(t, l, eval, &app) - vb := endBlock(t, l, eval) - appID := vb.Block().Payset[0].ApplicationID - require.NotZero(t, appID) - - // Give the app a lot of money - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: appID.Address(), - Amount: 1_000_000_000, - } - eval = nextBlock(t, l) - txn(t, l, eval, &fund) - endBlock(t, l, eval) + // Create the app + vb := dl.fullBlock(&app) + appID := vb.Block().Payset[0].ApplicationID + require.NotZero(t, appID) - require.Equal(t, 1_000_000_000, int(micros(t, l, appID.Address()))) + // Give the app a lot of money + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: appID.Address(), + Amount: 1_000_000_000, + } + dl.fullBlock(&fund) - // Build up Residue in RewardsState so it's ready to pay - for i := 1; i < 10; i++ { - eval := nextBlock(t, l) - endBlock(t, l, eval) - } + require.Equal(t, 1_000_000_000, int(micros(t, dl.generator, appID.Address()))) - // pay a little - pay := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: appID, - ApplicationArgs: [][]byte{[]byte("pay")}, - } - eval = nextBlock(t, l) - txn(t, l, eval, &pay) - endBlock(t, l, eval) - // 2000 was earned in rewards (- 1000 fee, -1 pay) - require.Equal(t, 1_000_000_999, int(micros(t, l, appID.Address()))) - - // Go nonpart - nonpart := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: appID, - ApplicationArgs: [][]byte{[]byte("nonpart")}, - } - eval = nextBlock(t, l) - txn(t, l, eval, &nonpart) - endBlock(t, l, eval) - require.Equal(t, 999_999_999, int(micros(t, l, appID.Address()))) - - // Build up Residue in RewardsState so it's ready to pay AGAIN - // But expect no rewards - for i := 1; i < 100; i++ { - eval := nextBlock(t, l) - endBlock(t, l, eval) - } - eval = nextBlock(t, l) - txn(t, l, eval, pay.Noted("again")) - txn(t, l, eval, nonpart.Noted("again"), "cannot change online/offline") - endBlock(t, l, eval) - // Paid fee + 1. Did not get rewards - require.Equal(t, 999_998_998, int(micros(t, l, appID.Address()))) + // Build up Residue in RewardsState so it's ready to pay + for i := 1; i < 10; i++ { + dl.fullBlock() + } + + // pay a little + pay := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: appID, + ApplicationArgs: [][]byte{[]byte("pay")}, + } + dl.fullBlock(&pay) + // 2000 was earned in rewards (- 1000 fee, -1 pay) + require.Equal(t, 1_000_000_999, int(micros(t, dl.generator, appID.Address()))) + + // Go nonpart + nonpart := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: appID, + ApplicationArgs: [][]byte{[]byte("nonpart")}, + } + dl.fullBlock(&nonpart) + require.Equal(t, 999_999_999, int(micros(t, dl.generator, appID.Address()))) + + // Build up Residue in RewardsState so it's ready to pay AGAIN + // But expect no rewards + for i := 1; i < 100; i++ { + dl.fullBlock() + } + dl.txn(pay.Noted("again")) + dl.txn(nonpart.Noted("again"), "cannot change online/offline") + require.Equal(t, 999_998_998, int(micros(t, dl.generator, appID.Address()))) + }) } func TestInnerAppCall(t *testing.T) { @@ -1205,13 +1196,16 @@ func TestInnerAppCall(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - app0 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 31 allowed inner appl. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + app0 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int pay itxn_field TypeEnum @@ -1221,16 +1215,14 @@ func TestInnerAppCall(t *testing.T) { itxn_field Receiver itxn_submit `), - } - eval := nextBlock(t, l) - txn(t, l, eval, &app0) - vb := endBlock(t, l, eval) - id0 := vb.Block().Payset[0].ApplicationID + } + vb := dl.fullBlock(&app0) + id0 := vb.Block().Payset[0].ApplicationID - app1 := txntest.Txn{ - Type: "appl", - Sender: addrs[1], - ApprovalProgram: main(` + app1 := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -1238,32 +1230,28 @@ func TestInnerAppCall(t *testing.T) { itxn_field ApplicationID itxn_submit `), - } + } - eval = nextBlock(t, l) - txns(t, l, eval, &app1) - vb = endBlock(t, l, eval) - id1 := vb.Block().Payset[0].ApplicationID + vb = dl.fullBlock(&app1) + id1 := vb.Block().Payset[0].ApplicationID - fund0 := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: id0.Address(), - Amount: 1_000_000_000, - } - fund1 := fund0 - fund1.Receiver = id1.Address() - - call1 := txntest.Txn{ - Type: "appl", - Sender: addrs[2], - ApplicationID: id1, - ForeignApps: []basics.AppIndex{id0}, - } - eval = nextBlock(t, l) - txns(t, l, eval, &fund0, &fund1, &call1) - endBlock(t, l, eval) + fund0 := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: id0.Address(), + Amount: 1_000_000_000, + } + fund1 := fund0 + fund1.Receiver = id1.Address() + call1 := txntest.Txn{ + Type: "appl", + Sender: addrs[2], + ApplicationID: id1, + ForeignApps: []basics.AppIndex{id0}, + } + dl.fullBlock(&fund0, &fund1, &call1) + }) } // TestInnerAppManipulate ensures that apps called from inner transactions make @@ -1496,13 +1484,16 @@ func TestIndirectReentry(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - app0 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 31 allowed inner appl. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + app0 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -1512,23 +1503,21 @@ func TestIndirectReentry(t *testing.T) { itxn_field Applications itxn_submit `), - } - eval := nextBlock(t, l) - txn(t, l, eval, &app0) - vb := endBlock(t, l, eval) - id0 := vb.Block().Payset[0].ApplicationID - - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: id0.Address(), - Amount: 1_000_000, - } + } + vb := dl.fullBlock(&app0) + id0 := vb.Block().Payset[0].ApplicationID - app1 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: id0.Address(), + Amount: 1_000_000, + } + + app1 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -1536,21 +1525,18 @@ func TestIndirectReentry(t *testing.T) { itxn_field ApplicationID itxn_submit `), - } - eval = nextBlock(t, l) - txns(t, l, eval, &app1, &fund) - vb = endBlock(t, l, eval) - id1 := vb.Block().Payset[0].ApplicationID - - call1 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: id0, - ForeignApps: []basics.AppIndex{id1, id0}, - } - eval = nextBlock(t, l) - txn(t, l, eval, &call1, "attempt to re-enter") - endBlock(t, l, eval) + } + vb = dl.fullBlock(&app1, &fund) + id1 := vb.Block().Payset[0].ApplicationID + + call1 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id1, id0}, + } + dl.txn(&call1, "attempt to re-enter") + }) } // TestValidAppReentry tests a valid form of reentry (which may not be the correct word here). @@ -1561,13 +1547,16 @@ func TestValidAppReentry(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - app0 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // 31 allowed inner appl. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + app0 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -1584,38 +1573,34 @@ func TestValidAppReentry(t *testing.T) { itxn_field Applications itxn_submit `), - } - eval := nextBlock(t, l) - txn(t, l, eval, &app0) - vb := endBlock(t, l, eval) - id0 := vb.Block().Payset[0].ApplicationID - - fund0 := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: id0.Address(), - Amount: 1_000_000, - } + } + vb := dl.fullBlock(&app0) + id0 := vb.Block().Payset[0].ApplicationID - app1 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + fund0 := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: id0.Address(), + Amount: 1_000_000, + } + + app1 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` int 3 int 3 == assert `), - } - eval = nextBlock(t, l) - txns(t, l, eval, &app1, &fund0) - vb = endBlock(t, l, eval) - id1 := vb.Block().Payset[0].ApplicationID + } + vb = dl.fullBlock(&app1, &fund0) + id1 := vb.Block().Payset[0].ApplicationID - app2 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + app2 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -1623,32 +1608,27 @@ func TestValidAppReentry(t *testing.T) { itxn_field ApplicationID itxn_submit `), - } - eval = nextBlock(t, l) - txn(t, l, eval, &app2) - vb = endBlock(t, l, eval) - id2 := vb.Block().Payset[0].ApplicationID - - fund2 := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: id2.Address(), - Amount: 1_000_000, - } + } + vb = dl.fullBlock(&app2) + id2 := vb.Block().Payset[0].ApplicationID + + fund2 := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: id2.Address(), + Amount: 1_000_000, + } - eval = nextBlock(t, l) - txn(t, l, eval, &fund2) - _ = endBlock(t, l, eval) + dl.txn(&fund2) - call1 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: id0, - ForeignApps: []basics.AppIndex{id2, id1, id0}, - } - eval = nextBlock(t, l) - txn(t, l, eval, &call1) - endBlock(t, l, eval) + call1 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id2, id1, id0}, + } + dl.txn(&call1) + }) } func TestMaxInnerTxForSingleAppCall(t *testing.T) { @@ -1750,13 +1730,15 @@ func TestAbortWhenInnerAppCallFails(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() + // 31 allowed inner appl. + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - app0 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + app0 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -1768,44 +1750,39 @@ int 1 == assert `), - } - eval := nextBlock(t, l) - txn(t, l, eval, &app0) - vb := endBlock(t, l, eval) - id0 := vb.Block().Payset[0].ApplicationID - - fund0 := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: id0.Address(), - Amount: 1_000_000, - } + } + vb := dl.fullBlock(&app0) + id0 := vb.Block().Payset[0].ApplicationID - app1 := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + fund0 := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: id0.Address(), + Amount: 1_000_000, + } + + app1 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` int 3 int 2 == assert `), - } - eval = nextBlock(t, l) - txns(t, l, eval, &app1, &fund0) - vb = endBlock(t, l, eval) - id1 := vb.Block().Payset[0].ApplicationID - - callTx := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: id0, - ForeignApps: []basics.AppIndex{id1}, - } + } + vb = dl.fullBlock(&app1, &fund0) + id1 := vb.Block().Payset[0].ApplicationID + + callTx := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: id0, + ForeignApps: []basics.AppIndex{id1}, + } - eval = nextBlock(t, l) - txn(t, l, eval, &callTx, "logic eval error") - endBlock(t, l, eval) + dl.txn(&callTx, "logic eval error") + }) } // TestSelfCheckHoldingNewApp checks whether a newly created app can check its @@ -2043,51 +2020,40 @@ func TestAppVersionMatching(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() - - four, err := logic.AssembleStringWithVersion("int 1", 4) - require.NoError(t, err) - five, err := logic.AssembleStringWithVersion("int 1", 5) - require.NoError(t, err) - six, err := logic.AssembleStringWithVersion("int 1", 6) - require.NoError(t, err) - create := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: five.Program, - ClearStateProgram: five.Program, - } - - eval := nextBlock(t, l) - txn(t, l, eval, &create) - endBlock(t, l, eval) - - create.ClearStateProgram = six.Program - - eval = nextBlock(t, l) - txn(t, l, eval, &create, "version mismatch") - endBlock(t, l, eval) + // matching required in v6 which is v31 + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - create.ApprovalProgram = six.Program + four, err := logic.AssembleStringWithVersion("int 1", 4) + require.NoError(t, err) + five, err := logic.AssembleStringWithVersion("int 1", 5) + require.NoError(t, err) + six, err := logic.AssembleStringWithVersion("int 1", 6) + require.NoError(t, err) - eval = nextBlock(t, l) - txn(t, l, eval, &create) - endBlock(t, l, eval) + create := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: five.Program, + ClearStateProgram: five.Program, + } + dl.txn(&create) - create.ClearStateProgram = four.Program + create.ClearStateProgram = six.Program + dl.txn(&create, "version mismatch") - eval = nextBlock(t, l) - txn(t, l, eval, &create, "version mismatch") - endBlock(t, l, eval) + create.ApprovalProgram = six.Program + dl.txn(&create) - // four doesn't match five, but it doesn't have to - create.ApprovalProgram = five.Program + create.ClearStateProgram = four.Program + dl.txn(&create, "version mismatch") - eval = nextBlock(t, l) - txn(t, l, eval, &create) - endBlock(t, l, eval) + // four doesn't match five, but it doesn't have to + create.ApprovalProgram = five.Program + dl.txn(&create) + }) } func TestAppDowngrade(t *testing.T) { @@ -2453,34 +2419,34 @@ func TestInnerClearState(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() + // Inner apps start in v31 + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - // inner will be an app that we opt into, then clearstate - // note that clearstate rejects - inner := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: "int 1", - ClearStateProgram: "int 0", - LocalStateSchema: basics.StateSchema{ - NumUint: 2, - NumByteSlice: 2, - }, - } + // inner will be an app that we opt into, then clearstate + // note that clearstate rejects + inner := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: "int 1", + ClearStateProgram: "int 0", + LocalStateSchema: basics.StateSchema{ + NumUint: 2, + NumByteSlice: 2, + }, + } - eval := nextBlock(t, l) - txn(t, l, eval, &inner) - vb := endBlock(t, l, eval) - innerID := vb.Block().Payset[0].ApplicationID + vb := dl.fullBlock(&inner) + innerID := vb.Block().Payset[0].ApplicationID - // Outer is a simple app that will invoke the given app (in ForeignApps[0]) - // with the given OnCompletion (in ApplicationArgs[0]). Goal is to use it - // to opt into, and the clear state, on the inner app. - outer := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // Outer is a simple app that will invoke the given app (in ForeignApps[0]) + // with the given OnCompletion (in ApplicationArgs[0]). Goal is to use it + // to opt into, and the clear state, on the inner app. + outer := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -2491,48 +2457,42 @@ itxn_begin itxn_field OnCompletion itxn_submit `), - ForeignApps: []basics.AppIndex{innerID}, - } + ForeignApps: []basics.AppIndex{innerID}, + } - eval = nextBlock(t, l) - txn(t, l, eval, &outer) - vb = endBlock(t, l, eval) - outerID := vb.Block().Payset[0].ApplicationID + vb = dl.fullBlock(&outer) + outerID := vb.Block().Payset[0].ApplicationID - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: outerID.Address(), - Amount: 1_000_000, - } + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: outerID.Address(), + Amount: 1_000_000, + } - call := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: outerID, - ApplicationArgs: [][]byte{{byte(transactions.OptInOC)}}, - ForeignApps: []basics.AppIndex{innerID}, - } - eval = nextBlock(t, l) - txns(t, l, eval, &fund, &call) - endBlock(t, l, eval) - - outerAcct := lookup(t, l, outerID.Address()) - require.Len(t, outerAcct.AppLocalStates, 1) - require.Equal(t, outerAcct.TotalAppSchema, basics.StateSchema{ - NumUint: 2, - NumByteSlice: 2, - }) + call := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: outerID, + ApplicationArgs: [][]byte{{byte(transactions.OptInOC)}}, + ForeignApps: []basics.AppIndex{innerID}, + } + dl.txns(&fund, &call) - call.ApplicationArgs = [][]byte{{byte(transactions.ClearStateOC)}} - eval = nextBlock(t, l) - txn(t, l, eval, &call) - endBlock(t, l, eval) + outerAcct := lookup(t, dl.generator, outerID.Address()) + require.Len(t, outerAcct.AppLocalStates, 1) + require.Equal(t, outerAcct.TotalAppSchema, basics.StateSchema{ + NumUint: 2, + NumByteSlice: 2, + }) - outerAcct = lookup(t, l, outerID.Address()) - require.Empty(t, outerAcct.AppLocalStates) - require.Empty(t, outerAcct.TotalAppSchema) + call.ApplicationArgs = [][]byte{{byte(transactions.ClearStateOC)}} + dl.txn(&call) + outerAcct = lookup(t, dl.generator, outerID.Address()) + require.Empty(t, outerAcct.AppLocalStates) + require.Empty(t, outerAcct.TotalAppSchema) + }) } // TestInnerClearStateBadCallee ensures that inner clear state programs are not @@ -2542,34 +2502,34 @@ func TestInnerClearStateBadCallee(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() + // Inner appls start in v31 + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - // badCallee tries to run down your budget, so an inner clear must be - // protected from exhaustion - badCallee := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: "int 1", - ClearStateProgram: `top: + // badCallee tries to run down your budget, so an inner clear must be + // protected from exhaustion + badCallee := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: "int 1", + ClearStateProgram: `top: int 1 pop b top `, - } + } - eval := nextBlock(t, l) - txn(t, l, eval, &badCallee) - vb := endBlock(t, l, eval) - badID := vb.Block().Payset[0].ApplicationID + vb := dl.fullBlock(&badCallee) + badID := vb.Block().Payset[0].ApplicationID - // Outer is a simple app that will invoke the given app (in ForeignApps[0]) - // with the given OnCompletion (in ApplicationArgs[0]). Goal is to use it - // to opt into, and then clear state, the bad app - outer := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // Outer is a simple app that will invoke the given app (in ForeignApps[0]) + // with the given OnCompletion (in ApplicationArgs[0]). Goal is to use it + // to opt into, and then clear state, the bad app + outer := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -2597,44 +2557,39 @@ bnz skip // Don't do budget checking during optin assert skip: `), - ForeignApps: []basics.AppIndex{badID}, - } + ForeignApps: []basics.AppIndex{badID}, + } - eval = nextBlock(t, l) - txn(t, l, eval, &outer) - vb = endBlock(t, l, eval) - outerID := vb.Block().Payset[0].ApplicationID + vb = dl.fullBlock(&outer) + outerID := vb.Block().Payset[0].ApplicationID - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: outerID.Address(), - Amount: 1_000_000, - } + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: outerID.Address(), + Amount: 1_000_000, + } - call := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: outerID, - ApplicationArgs: [][]byte{{byte(transactions.OptInOC)}}, - ForeignApps: []basics.AppIndex{badID}, - } - eval = nextBlock(t, l) - txns(t, l, eval, &fund, &call) - endBlock(t, l, eval) - - outerAcct := lookup(t, l, outerID.Address()) - require.Len(t, outerAcct.AppLocalStates, 1) - - // When doing a clear state, `call` checks that budget wasn't stolen - call.ApplicationArgs = [][]byte{{byte(transactions.ClearStateOC)}} - eval = nextBlock(t, l) - txn(t, l, eval, &call) - endBlock(t, l, eval) - - // Clearstate took effect, despite failure from infinite loop - outerAcct = lookup(t, l, outerID.Address()) - require.Empty(t, outerAcct.AppLocalStates) + call := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: outerID, + ApplicationArgs: [][]byte{{byte(transactions.OptInOC)}}, + ForeignApps: []basics.AppIndex{badID}, + } + dl.fullBlock(&fund, &call) + + outerAcct := lookup(t, dl.generator, outerID.Address()) + require.Len(t, outerAcct.AppLocalStates, 1) + + // When doing a clear state, `call` checks that budget wasn't stolen + call.ApplicationArgs = [][]byte{{byte(transactions.ClearStateOC)}} + dl.fullBlock(&call) + + // Clearstate took effect, despite failure from infinite loop + outerAcct = lookup(t, dl.generator, outerID.Address()) + require.Empty(t, outerAcct.AppLocalStates) + }) } // TestInnerClearStateBadCaller ensures that inner clear state programs cannot @@ -2644,28 +2599,30 @@ func TestInnerClearStateBadCaller(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() + // Inner appls start in v31 + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - inner := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: "int 1", - ClearStateProgram: `global OpcodeBudget + inner := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: "int 1", + ClearStateProgram: `global OpcodeBudget itob log int 1`, - LocalStateSchema: basics.StateSchema{ - NumUint: 1, - NumByteSlice: 2, - }, - } + LocalStateSchema: basics.StateSchema{ + NumUint: 1, + NumByteSlice: 2, + }, + } - // waster allows tries to get the budget down below 100 before returning - waster := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // waster allows tries to get the budget down below 100 before returning + waster := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` global OpcodeBudget itob log @@ -2683,25 +2640,23 @@ global OpcodeBudget itob log `), - LocalStateSchema: basics.StateSchema{ - NumUint: 3, - NumByteSlice: 4, - }, - } + LocalStateSchema: basics.StateSchema{ + NumUint: 3, + NumByteSlice: 4, + }, + } - eval := nextBlock(t, l) - txns(t, l, eval, &inner, &waster) - vb := endBlock(t, l, eval) - innerID := vb.Block().Payset[0].ApplicationID - wasterID := vb.Block().Payset[1].ApplicationID + vb := dl.fullBlock(&inner, &waster) + innerID := vb.Block().Payset[0].ApplicationID + wasterID := vb.Block().Payset[1].ApplicationID - // Grouper is a simple app that will invoke the given apps (in - // ForeignApps[0,1]) as a group, with the given OnCompletion (in - // ApplicationArgs[0]). - grouper := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + // Grouper is a simple app that will invoke the given apps (in + // ForeignApps[0,1]) as a group, with the given OnCompletion (in + // ApplicationArgs[0]). + grouper := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` itxn_begin int appl itxn_field TypeEnum @@ -2720,43 +2675,37 @@ itxn_next itxn_field OnCompletion itxn_submit `), - } + } - eval = nextBlock(t, l) - txn(t, l, eval, &grouper) - vb = endBlock(t, l, eval) - grouperID := vb.Block().Payset[0].ApplicationID + vb = dl.fullBlock(&grouper) + grouperID := vb.Block().Payset[0].ApplicationID - fund := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: grouperID.Address(), - Amount: 1_000_000, - } + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: grouperID.Address(), + Amount: 1_000_000, + } - call := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: grouperID, - ApplicationArgs: [][]byte{{byte(transactions.OptInOC)}, {byte(transactions.OptInOC)}}, - ForeignApps: []basics.AppIndex{wasterID, innerID}, - } - eval = nextBlock(t, l) - txns(t, l, eval, &fund, &call) - endBlock(t, l, eval) - - gAcct := lookup(t, l, grouperID.Address()) - require.Len(t, gAcct.AppLocalStates, 2) - - call.ApplicationArgs = [][]byte{{byte(transactions.CloseOutOC)}, {byte(transactions.ClearStateOC)}} - eval = nextBlock(t, l) - txn(t, l, eval, &call, "ClearState execution with low OpcodeBudget") - vb = endBlock(t, l, eval) - require.Len(t, vb.Block().Payset, 0) - - // Clearstate did not take effect, since the caller tried to shortchange the CSP - gAcct = lookup(t, l, grouperID.Address()) - require.Len(t, gAcct.AppLocalStates, 2) + call := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: grouperID, + ApplicationArgs: [][]byte{{byte(transactions.OptInOC)}, {byte(transactions.OptInOC)}}, + ForeignApps: []basics.AppIndex{wasterID, innerID}, + } + dl.fullBlock(&fund, &call) + + gAcct := lookup(t, dl.generator, grouperID.Address()) + require.Len(t, gAcct.AppLocalStates, 2) + + call.ApplicationArgs = [][]byte{{byte(transactions.CloseOutOC)}, {byte(transactions.ClearStateOC)}} + dl.txn(&call, "ClearState execution with low OpcodeBudget") + + // Clearstate did not take effect, since the caller tried to shortchange the CSP + gAcct = lookup(t, dl.generator, grouperID.Address()) + require.Len(t, gAcct.AppLocalStates, 2) + }) } // TestClearStateInnerPay ensures that ClearState programs can run inner txns in @@ -2880,13 +2829,15 @@ func TestGlobalChangesAcrossApps(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() + // Inner appls start in v31 + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - appA := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + appA := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` // Call B : No arguments means: set your global "X" to "ABC" itxn_begin int appl; itxn_field TypeEnum @@ -2916,12 +2867,12 @@ func TestGlobalChangesAcrossApps(t *testing.T) { == assert `), - } + } - appB := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + appB := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` txn NumAppArgs bnz check // 1 arg means check // set @@ -2937,15 +2888,15 @@ check: assert b end `), - GlobalStateSchema: basics.StateSchema{ - NumByteSlice: 1, - }, - } + GlobalStateSchema: basics.StateSchema{ + NumByteSlice: 1, + }, + } - appC := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + appC := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` txn Applications 1 byte "X" app_global_get_ex @@ -2954,32 +2905,29 @@ check: == assert `), - } + } - eval := nextBlock(t, l) - txns(t, l, eval, &appA, &appB, &appC) - vb := endBlock(t, l, eval) - idA := vb.Block().Payset[0].ApplicationID - idB := vb.Block().Payset[1].ApplicationID - idC := vb.Block().Payset[2].ApplicationID - - fundA := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: idA.Address(), - Amount: 1_000_000, - } + vb := dl.fullBlock(&appA, &appB, &appC) + idA := vb.Block().Payset[0].ApplicationID + idB := vb.Block().Payset[1].ApplicationID + idC := vb.Block().Payset[2].ApplicationID - callA := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: idA, - ForeignApps: []basics.AppIndex{idB, idC}, - } + fundA := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: idA.Address(), + Amount: 1_000_000, + } - eval = nextBlock(t, l) - txns(t, l, eval, &fundA, &callA) - endBlock(t, l, eval) + callA := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: idA, + ForeignApps: []basics.AppIndex{idB, idC}, + } + + dl.fullBlock(&fundA, &callA) + }) } // TestLocalChangesAcrossApps ensures that state changes are seen by other app @@ -2989,13 +2937,15 @@ func TestLocalChangesAcrossApps(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - l := newTestLedger(t, genBalances) - defer l.Close() + // Inner appls start in v31 + ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() - appA := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + appA := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` // Call B : No arguments means: set caller's local "X" to "ABC" itxn_begin int appl; itxn_field TypeEnum @@ -3027,12 +2977,12 @@ func TestLocalChangesAcrossApps(t *testing.T) { == assert `), - } + } - appB := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + appB := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` txn NumAppArgs bnz check // 1 arg means check // set @@ -3050,15 +3000,15 @@ check: assert b end `), - LocalStateSchema: basics.StateSchema{ - NumByteSlice: 1, - }, - } + LocalStateSchema: basics.StateSchema{ + NumByteSlice: 1, + }, + } - appC := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: main(` + appC := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` txn Sender txn Applications 1 byte "X" @@ -3068,32 +3018,29 @@ check: == assert `), - } + } - eval := nextBlock(t, l) - txns(t, l, eval, &appA, &appB, &appC) - vb := endBlock(t, l, eval) - idA := vb.Block().Payset[0].ApplicationID - idB := vb.Block().Payset[1].ApplicationID - idC := vb.Block().Payset[2].ApplicationID - - fundA := txntest.Txn{ - Type: "pay", - Sender: addrs[0], - Receiver: idA.Address(), - Amount: 1_000_000, - } + vb := dl.fullBlock(&appA, &appB, &appC) + idA := vb.Block().Payset[0].ApplicationID + idB := vb.Block().Payset[1].ApplicationID + idC := vb.Block().Payset[2].ApplicationID - callA := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApplicationID: idA, - ForeignApps: []basics.AppIndex{idB, idC}, - } + fundA := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: idA.Address(), + Amount: 1_000_000, + } - eval = nextBlock(t, l) - txns(t, l, eval, &fundA, &callA) - endBlock(t, l, eval) + callA := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApplicationID: idA, + ForeignApps: []basics.AppIndex{idB, idC}, + } + + dl.fullBlock(&fundA, &callA) + }) } func TestForeignAppAccountsAccessible(t *testing.T) { diff --git a/ledger/double_test.go b/ledger/double_test.go index 0854943636..c1cd09f97c 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -153,23 +153,24 @@ func (dl *DoubleLedger) endBlock(proposer ...basics.Address) *ledgercore.Validat return vb } -func (dl *DoubleLedger) fundedApp(sender basics.Address, amount uint64, source string) basics.AppIndex { +func (dl *DoubleLedger) createApp(sender basics.Address, source string) basics.AppIndex { createapp := txntest.Txn{ Type: "appl", Sender: sender, ApprovalProgram: source, } vb := dl.fullBlock(&createapp) - appIndex := vb.Block().Payset[0].ApplyData.ApplicationID + return vb.Block().Payset[0].ApplyData.ApplicationID +} - fund := txntest.Txn{ +func (dl *DoubleLedger) fundedApp(sender basics.Address, amount uint64, source string) basics.AppIndex { + appIndex := dl.createApp(sender, source) + dl.fullBlock(&txntest.Txn{ Type: "pay", Sender: sender, Receiver: appIndex.Address(), Amount: amount, - } - - dl.txn(&fund) + }) return appIndex } diff --git a/ledger/eval/appcow_test.go b/ledger/eval/appcow_test.go index 60ab1df25e..6f5e39b305 100644 --- a/ledger/eval/appcow_test.go +++ b/ledger/eval/appcow_test.go @@ -48,6 +48,14 @@ func (ml *emptyLedger) lookup(addr basics.Address) (ledgercore.AccountData, erro return ledgercore.AccountData{}, nil } +func (ml *emptyLedger) lookupAgreement(addr basics.Address) (basics.OnlineAccountData, error) { + return basics.OnlineAccountData{}, nil +} + +func (ml *emptyLedger) onlineStake() (basics.MicroAlgos, error) { + return basics.MicroAlgos{}, nil +} + func (ml *emptyLedger) lookupAppParams(addr basics.Address, aidx basics.AppIndex, cacheOnly bool) (ledgercore.AppParamsDelta, bool, error) { return ledgercore.AppParamsDelta{}, true, nil } diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 3aa7bd3905..c1d5d6d619 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -38,6 +38,23 @@ func (cs *roundCowState) AccountData(addr basics.Address) (ledgercore.AccountDat return record, nil } +func (cs *roundCowState) AgreementData(addr basics.Address) (basics.OnlineAccountData, error) { + record, err := cs.lookupAgreement(addr) + if err != nil { + return basics.OnlineAccountData{}, err + } + return record, nil +} + +func (cs *roundCowState) OnlineStake() (basics.MicroAlgos, error) { + return cs.lookupParent.onlineStake() +} + +// onlineStake is needed to implement roundCowParent +func (cs *roundCowState) onlineStake() (basics.MicroAlgos, error) { + return cs.lookupParent.onlineStake() +} + func (cs *roundCowState) Authorizer(addr basics.Address) (basics.Address, error) { record, err := cs.Get(addr, false) // pending rewards unneeded if err != nil { diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 23c415bbdf..9511af7ce7 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -44,6 +44,10 @@ type roundCowParent interface { // lookup retrieves data about an address, eventually querying the ledger if the address was not found in cache. lookup(basics.Address) (ledgercore.AccountData, error) + // lookup retrieves agreement data about an address, querying the ledger if necessary. + lookupAgreement(basics.Address) (basics.OnlineAccountData, error) + onlineStake() (basics.MicroAlgos, error) + // lookupAppParams, lookupAssetParams, lookupAppLocalState, and lookupAssetHolding retrieve data for a given address and ID. // If cacheOnly is set, the ledger DB will not be queried, and only the cache will be consulted. // This is used when we know a given value is already in cache (from a previous query for that same address and ID), @@ -182,6 +186,12 @@ func (cb *roundCowState) lookup(addr basics.Address) (data ledgercore.AccountDat return cb.lookupParent.lookup(addr) } +// lookupAgreement differs from other lookup methods because it need not +// maintain a local value because it cannot be modified by transactions. +func (cb *roundCowState) lookupAgreement(addr basics.Address) (data basics.OnlineAccountData, err error) { + return cb.lookupParent.lookupAgreement(addr) +} + func (cb *roundCowState) lookupAppParams(addr basics.Address, aidx basics.AppIndex, cacheOnly bool) (ledgercore.AppParamsDelta, bool, error) { params, ok := cb.mods.Accts.GetAppParams(addr, aidx) if ok { diff --git a/ledger/eval/cow_test.go b/ledger/eval/cow_test.go index 81224a8d0b..138e2562ad 100644 --- a/ledger/eval/cow_test.go +++ b/ledger/eval/cow_test.go @@ -44,6 +44,35 @@ func (ml *mockLedger) lookup(addr basics.Address) (ledgercore.AccountData, error return ledgercore.ToAccountData(ml.balanceMap[addr]), nil } +// convertToOnline is only suitable for test code because OnlineAccountData +// should have rewards paid. Here, we ignore that for simple tests. +func convertToOnline(ad ledgercore.AccountData) basics.OnlineAccountData { + return basics.OnlineAccountData{ + MicroAlgosWithRewards: ad.MicroAlgos, + VotingData: basics.VotingData{ + VoteID: ad.VoteID, + SelectionID: ad.SelectionID, + StateProofID: ad.StateProofID, + VoteFirstValid: ad.VoteFirstValid, + VoteLastValid: ad.VoteLastValid, + VoteKeyDilution: ad.VoteKeyDilution, + }, + IncentiveEligible: ad.IncentiveEligible, + } +} + +func (ml *mockLedger) lookupAgreement(addr basics.Address) (basics.OnlineAccountData, error) { + ad, err := ml.lookup(addr) + if err != nil { // impossible, see lookup() + return basics.OnlineAccountData{}, err + } + return convertToOnline(ad), nil +} + +func (ml *mockLedger) onlineStake() (basics.MicroAlgos, error) { + return basics.Algos(55_555), nil +} + func (ml *mockLedger) lookupAppParams(addr basics.Address, aidx basics.AppIndex, cacheOnly bool) (ledgercore.AppParamsDelta, bool, error) { params, ok := ml.balanceMap[addr].AppParams[aidx] return ledgercore.AppParamsDelta{Params: ¶ms}, ok, nil // XXX make a copy? diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 6599dd5918..859b62922f 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -24,6 +24,7 @@ import ( "math/bits" "sync" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -46,11 +47,13 @@ type LedgerForCowBase interface { GenesisHash() crypto.Digest CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error LookupWithoutRewards(basics.Round, basics.Address) (ledgercore.AccountData, basics.Round, error) + LookupAgreement(basics.Round, basics.Address) (basics.OnlineAccountData, error) LookupAsset(basics.Round, basics.Address, basics.AssetIndex) (ledgercore.AssetResource, error) LookupApplication(basics.Round, basics.Address, basics.AppIndex) (ledgercore.AppResource, error) LookupKv(basics.Round, string) ([]byte, error) GetCreatorForRound(basics.Round, basics.CreatableIndex, basics.CreatableType) (basics.Address, bool, error) GetStateProofVerificationContext(stateProofLastAttestedRound basics.Round) (*ledgercore.StateProofVerificationContext, error) + OnlineCirculation(basics.Round, basics.Round) (basics.MicroAlgos, error) } // ErrRoundZero is self-explanatory @@ -129,6 +132,15 @@ type roundCowBase struct { // The account data store here is always the account data without the rewards. accounts map[basics.Address]ledgercore.AccountData + // The online accounts that we've already accessed during this round evaluation. This is a + // cache used to avoid looking up the same account data more than once during a single evaluator + // execution. The OnlineAccountData is historical and therefore won't be changing. + onlineAccounts map[basics.Address]basics.OnlineAccountData + + // totalOnline is the cached amount of online stake for rnd (so it's from + // rnd-320). The zero value indicates it is not yet cached. + totalOnline basics.MicroAlgos + // Similarly to accounts cache that stores base account data, there are caches for params, states, holdings. appParams map[ledgercore.AccountApp]cachedAppParams assetParams map[ledgercore.AccountAsset]cachedAssetParams @@ -150,6 +162,7 @@ func makeRoundCowBase(l LedgerForCowBase, rnd basics.Round, txnCount uint64, sta stateProofNextRnd: stateProofNextRnd, proto: proto, accounts: make(map[basics.Address]ledgercore.AccountData), + onlineAccounts: make(map[basics.Address]basics.OnlineAccountData), appParams: make(map[ledgercore.AccountApp]cachedAppParams), assetParams: make(map[ledgercore.AccountAsset]cachedAssetParams), appLocalStates: make(map[ledgercore.AccountApp]cachedAppLocalState), @@ -193,6 +206,56 @@ func (x *roundCowBase) lookup(addr basics.Address) (ledgercore.AccountData, erro return ad, err } +// balanceRound reproduces the way that the agreement package finds the round to +// consider for online accounts. +func (x *roundCowBase) balanceRound() (basics.Round, error) { + phdr, err := x.BlockHdr(agreement.ParamsRound(x.rnd)) + if err != nil { + return 0, err + } + agreementParams := config.Consensus[phdr.CurrentProtocol] + return agreement.BalanceRound(x.rnd, agreementParams), nil +} + +// lookupAgreement returns the online accountdata for the provided account address. It uses an internal cache +// to avoid repeated lookups against the ledger. +func (x *roundCowBase) lookupAgreement(addr basics.Address) (basics.OnlineAccountData, error) { + if accountData, found := x.onlineAccounts[addr]; found { + return accountData, nil + } + + brnd, err := x.balanceRound() + if err != nil { + return basics.OnlineAccountData{}, err + } + ad, err := x.l.LookupAgreement(brnd, addr) + if err != nil { + return basics.OnlineAccountData{}, err + } + + x.onlineAccounts[addr] = ad + return ad, err +} + +// onlineStake returns the total online stake as of the start of the round. It +// caches the result to prevent repeated calls to the ledger. +func (x *roundCowBase) onlineStake() (basics.MicroAlgos, error) { + if !x.totalOnline.IsZero() { + return x.totalOnline, nil + } + + brnd, err := x.balanceRound() + if err != nil { + return basics.MicroAlgos{}, err + } + total, err := x.l.OnlineCirculation(brnd, x.rnd) + if err != nil { + return basics.MicroAlgos{}, err + } + x.totalOnline = total + return x.totalOnline, err +} + func (x *roundCowBase) updateAssetResourceCache(aa ledgercore.AccountAsset, r ledgercore.AssetResource) { // cache AssetParams and AssetHolding returned by LookupResource if r.AssetParams == nil { diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 994bedd561..77a477b3c0 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -180,6 +180,7 @@ ok: // and the usage counts correctly propagated from parent cow to child cow and back func TestEvalAppStateCountsWithTxnGroup(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() _, _, err := testEvalAppGroup(t, basics.StateSchema{NumByteSlice: 1}) require.ErrorContains(t, err, "store bytes count 2 exceeds schema bytes count 1") @@ -189,6 +190,7 @@ func TestEvalAppStateCountsWithTxnGroup(t *testing.T) { // produce correct results when a txn group has storage allocate and storage update actions func TestEvalAppAllocStateWithTxnGroup(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() eval, addr, err := testEvalAppGroup(t, basics.StateSchema{NumByteSlice: 2}) require.NoError(t, err) @@ -203,6 +205,7 @@ func TestEvalAppAllocStateWithTxnGroup(t *testing.T) { // see TestBlockEvaluator for more func TestTestTransactionGroup(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var txgroup []transactions.SignedTxn eval := BlockEvaluator{} @@ -219,6 +222,7 @@ func TestTestTransactionGroup(t *testing.T) { // some trivial checks that require no setup func TestPrivateTransactionGroup(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() var txgroup []transactions.SignedTxnWithAD eval := BlockEvaluator{} @@ -675,56 +679,6 @@ func testnetFixupExecution(t *testing.T, headerRound basics.Round, poolBonus uin require.NoError(t, err) } -// newTestGenesis creates a bunch of accounts, splits up 10B algos -// between them and the rewardspool and feesink, and gives out the -// addresses and secrets it creates to enable tests. For special -// scenarios, manipulate these return values before using newTestLedger. -func newTestGenesis() (bookkeeping.GenesisBalances, []basics.Address, []*crypto.SignatureSecrets) { - // irrelevant, but deterministic - sink, err := basics.UnmarshalChecksumAddress("YTPRLJ2KK2JRFSZZNAF57F3K5Y2KCG36FZ5OSYLW776JJGAUW5JXJBBD7Q") - if err != nil { - panic(err) - } - rewards, err := basics.UnmarshalChecksumAddress("242H5OXHUEBYCGGWB3CQ6AZAMQB5TMCWJGHCGQOZPEIVQJKOO7NZXUXDQA") - if err != nil { - panic(err) - } - - const count = 10 - addrs := make([]basics.Address, count) - secrets := make([]*crypto.SignatureSecrets, count) - accts := make(map[basics.Address]basics.AccountData) - - // 10 billion microalgos, across N accounts and pool and sink - amount := 10 * 1000000000 * 1000000 / uint64(count+2) - - for i := 0; i < count; i++ { - // Create deterministic addresses, so that output stays the same, run to run. - var seed crypto.Seed - seed[0] = byte(i) - secrets[i] = crypto.GenerateSignatureSecrets(seed) - addrs[i] = basics.Address(secrets[i].SignatureVerifier) - - adata := basics.AccountData{ - MicroAlgos: basics.MicroAlgos{Raw: amount}, - } - accts[addrs[i]] = adata - } - - accts[sink] = basics.AccountData{ - MicroAlgos: basics.MicroAlgos{Raw: amount}, - Status: basics.NotParticipating, - } - - accts[rewards] = basics.AccountData{ - MicroAlgos: basics.MicroAlgos{Raw: amount}, - } - - genBalances := bookkeeping.MakeGenesisBalances(accts, sink, rewards) - - return genBalances, addrs, secrets -} - type evalTestLedger struct { blocks map[basics.Round]bookkeeping.Block roundBalances map[basics.Round]map[basics.Address]basics.AccountData @@ -827,11 +781,21 @@ func (ledger *evalTestLedger) LatestTotals() (basics.Round, ledgercore.AccountTo return basics.Round(len(ledger.blocks)).SubSaturate(1), ledger.latestTotals, nil } -// LookupWithoutRewards is like Lookup but does not apply pending rewards up -// to the requested round rnd. +// LookupWithoutRewards is like Lookup but is not supposed to apply pending +// rewards up to the requested round rnd. Here Lookup doesn't do that anyway. func (ledger *evalTestLedger) LookupWithoutRewards(rnd basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, error) { - ad := ledger.roundBalances[rnd][addr] - return ledgercore.ToAccountData(ad), rnd, nil + ad, err := ledger.Lookup(rnd, addr) + return ledgercore.ToAccountData(ad), rnd, err +} + +func (ledger *evalTestLedger) LookupAgreement(rnd basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { + ad, _, err := ledger.LookupWithoutRewards(rnd, addr) + return convertToOnline(ad), err +} + +// OnlineCirculation just returns a deterministic value for a given round. +func (ledger *evalTestLedger) OnlineCirculation(rnd, voteRound basics.Round) (basics.MicroAlgos, error) { + return basics.MicroAlgos{Raw: uint64(rnd) * 1_000_000}, nil } func (ledger *evalTestLedger) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { @@ -1057,6 +1021,14 @@ func (l *testCowBaseLedger) LookupWithoutRewards(basics.Round, basics.Address) ( return ledgercore.AccountData{}, basics.Round(0), errors.New("not implemented") } +func (l *testCowBaseLedger) LookupAgreement(rnd basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { + return basics.OnlineAccountData{}, errors.New("not implemented") +} + +func (l *testCowBaseLedger) OnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { + return basics.MicroAlgos{}, errors.New("not implemented") +} + func (l *testCowBaseLedger) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { return ledgercore.AppResource{}, errors.New("not implemented") } diff --git a/ledger/eval/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go index cb4b165c94..734d84a661 100644 --- a/ledger/eval/prefetcher/prefetcher_alignment_test.go +++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go @@ -18,6 +18,7 @@ package prefetcher_test import ( "context" + "errors" "fmt" "testing" @@ -118,6 +119,16 @@ func (l *prefetcherAlignmentTestLedger) LookupWithoutRewards(_ basics.Round, add } return ledgercore.AccountData{}, 0, nil } +func (l *prefetcherAlignmentTestLedger) LookupAgreement(_ basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { + // prefetch alignment tests do not check for prefetching of online account data + // because it's quite different and can only occur in AVM opcodes, which + // aren't handled anyway (just as we don't know if a holding or app local + // will be accessed in AVM.) + return basics.OnlineAccountData{}, errors.New("not implemented") +} +func (l *prefetcherAlignmentTestLedger) OnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { + return basics.MicroAlgos{}, errors.New("not implemented") +} func (l *prefetcherAlignmentTestLedger) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { l.mu.Lock() if l.requestedApps == nil { diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go index 9ac67d2fb7..972821c26c 100644 --- a/ledger/eval_simple_test.go +++ b/ledger/eval_simple_test.go @@ -406,34 +406,84 @@ func TestAbsentTracking(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis(func(cfg *ledgertesting.GenesisCfg) { cfg.OnlineCount = 2 // So we know proposer should propose every 2 rounds, on average - }) + }, ledgertesting.TurnOffRewards) + getOnlineStake := ` + int 0; voter_params_get VoterBalance; itob; log; itob; log; + int 0; voter_params_get VoterIncentiveEligible; itob; log; itob; log; + int 1` + checkingBegins := 40 - ledgertesting.TestConsensusRange(t, checkingBegins-1, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + ledgertesting.TestConsensusRange(t, checkingBegins, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { dl := NewDoubleLedger(t, genBalances, cv, cfg) defer dl.Close() - // have addrs[1] go online, which makes it eligible for suspension - dl.txn(&txntest.Txn{ + // we use stakeChecker for testing `voter_params_get` on suspended accounts + stib := dl.txn(&txntest.Txn{ // #1 + Type: "appl", + Sender: addrs[3], // use non-online account, Totals unchanged + ApprovalProgram: getOnlineStake, + }) + stakeChecker := stib.ApplicationID + require.NotZero(t, stakeChecker) + + checkState := func(addr basics.Address, online bool, eligible bool, expected uint64) { + t.Helper() + if !online { + require.Zero(t, expected) + } + stib = dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addr, + ApplicationID: stakeChecker, + }) + logs := stib.ApplyData.EvalDelta.Logs + require.Len(t, logs, 4) + tBytes := "\x00\x00\x00\x00\x00\x00\x00\x01" + fBytes := "\x00\x00\x00\x00\x00\x00\x00\x00" + onlBytes := fBytes + if online { + onlBytes = tBytes + } + elgBytes := fBytes + if eligible { + elgBytes = tBytes + } + require.Equal(t, onlBytes, logs[0], "online") + require.Equal(t, int(expected), int(binary.BigEndian.Uint64([]byte(logs[1])))) + require.Equal(t, onlBytes, logs[2], "online") + require.Equal(t, elgBytes, logs[3], "eligible") + } + + // have addrs[1] go online explicitly, which makes it eligible for suspension. + // use a large fee, so we can see IncentiveEligible change + dl.txn(&txntest.Txn{ // #2 Type: "keyreg", + Fee: 10_000_000, Sender: addrs[1], VotePK: [32]byte{1}, SelectionPK: [32]byte{1}, }) - totals, err := dl.generator.Totals(1) - require.NoError(t, err) - require.NotZero(t, totals.Online.Money.Raw) - // as configured above, only the first two accounts should be online require.True(t, lookup(t, dl.generator, addrs[0]).Status == basics.Online) require.True(t, lookup(t, dl.generator, addrs[1]).Status == basics.Online) require.False(t, lookup(t, dl.generator, addrs[2]).Status == basics.Online) + checkState(addrs[0], true, false, 833_333_333_333_333) // #3 + require.Equal(t, int(lookup(t, dl.generator, addrs[0]).MicroAlgos.Raw), 833_333_333_332_333) + // although addr[1] just paid to be eligible, it won't be for 320 rounds + checkState(addrs[1], true, false, 833_333_333_333_333) // #4 + checkState(addrs[2], false, false, 0) // #5 + // genesis accounts don't begin IncentiveEligible, even if online require.False(t, lookup(t, dl.generator, addrs[0]).IncentiveEligible) - require.False(t, lookup(t, dl.generator, addrs[1]).IncentiveEligible) + // but addr[1] paid extra fee. Note this is _current_ state, not really "online" + require.True(t, lookup(t, dl.generator, addrs[1]).IncentiveEligible) require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) - dl.fullBlock() + vb := dl.fullBlock() // #6 + totals, err := dl.generator.Totals(vb.Block().Round()) + require.NoError(t, err) + require.NotZero(t, totals.Online.Money.Raw) // although it's not even online, we'll use addrs[7] as the proposer proposer := addrs[7] @@ -444,18 +494,14 @@ func TestAbsentTracking(t *testing.T) { Receiver: addrs[2], Amount: 100_000, }) - dl.endBlock(proposer) + dl.endBlock(proposer) // #7 prp := lookup(t, dl.validator, proposer) - if ver >= checkingBegins { - require.Equal(t, prp.LastProposed, dl.validator.Latest()) - } else { - require.Zero(t, prp.LastProposed) - } + require.Equal(t, prp.LastProposed, dl.validator.Latest()) require.Zero(t, prp.LastHeartbeat) require.False(t, prp.IncentiveEligible) - // addr[1] is spent to an offline account, so Online totals decrease + // addr[1] paid to an offline account, so Online totals decrease newtotals, err := dl.generator.Totals(dl.generator.Latest()) require.NoError(t, err) // payment and fee left the online account @@ -465,15 +511,15 @@ func TestAbsentTracking(t *testing.T) { dl.fullBlock() // addrs[2] was already offline - dl.txns(&txntest.Txn{Type: "keyreg", Sender: addrs[2]}) // OFFLINE keyreg + dl.txns(&txntest.Txn{Type: "keyreg", Sender: addrs[2]}) // OFFLINE keyreg #9 regger := lookup(t, dl.validator, addrs[2]) - // total were unchanged by an offline keyreg from an offline account + // totals were unchanged by an offline keyreg from an offline account newtotals, err = dl.generator.Totals(dl.generator.Latest()) require.NoError(t, err) require.Equal(t, totals.Online.Money.Raw, newtotals.Online.Money.Raw) - // an an offline keyreg transaction records no activity + // an offline keyreg transaction records no activity require.Zero(t, regger.LastProposed) require.Zero(t, regger.LastHeartbeat) @@ -483,8 +529,8 @@ func TestAbsentTracking(t *testing.T) { Sender: addrs[2], VotePK: [32]byte{1}, SelectionPK: [32]byte{1}, - }) - // online totals have grown + }) // #10 + // online totals have grown, addr[2] was added newtotals, err = dl.generator.Totals(dl.generator.Latest()) require.NoError(t, err) require.Greater(t, newtotals.Online.Money.Raw, totals.Online.Money.Raw) @@ -493,27 +539,30 @@ func TestAbsentTracking(t *testing.T) { require.Zero(t, regger.LastProposed) require.True(t, regger.Status == basics.Online) - if ver >= checkingBegins { - require.NotZero(t, regger.LastHeartbeat) // online keyreg caused update - } else { - require.Zero(t, regger.LastHeartbeat) - } + // But nothing has changed, since we're not past 320 + checkState(addrs[0], true, false, 833_333_333_333_333) // #11 + checkState(addrs[1], true, false, 833_333_333_333_333) // #12 + checkState(addrs[2], false, false, 0) // #13 + + require.NotZero(t, regger.LastHeartbeat) // online keyreg caused update require.False(t, regger.IncentiveEligible) // ONLINE keyreg with extra fee - dl.txns(&txntest.Txn{ + vb = dl.fullBlock(&txntest.Txn{ Type: "keyreg", Fee: 2_000_000, Sender: addrs[2], VotePK: [32]byte{1}, SelectionPK: [32]byte{1}, - }) + }) // #14 + twoEligible := vb.Block().Round() + require.EqualValues(t, 14, twoEligible) // sanity check regger = lookup(t, dl.validator, addrs[2]) - require.Equal(t, ver >= checkingBegins, regger.IncentiveEligible) + require.True(t, regger.IncentiveEligible) for i := 0; i < 5; i++ { - dl.fullBlock() + dl.fullBlock() // #15-19 require.True(t, lookup(t, dl.generator, addrs[0]).Status == basics.Online) require.True(t, lookup(t, dl.generator, addrs[1]).Status == basics.Online) require.True(t, lookup(t, dl.generator, addrs[2]).Status == basics.Online) @@ -525,7 +574,7 @@ func TestAbsentTracking(t *testing.T) { require.True(t, lookup(t, dl.generator, addrs[2]).Status == basics.Online) for i := 0; i < 30; i++ { - dl.fullBlock() + dl.fullBlock() // #20-49 } // addrs 0-2 all have about 1/3 of stake, so seemingly (see next block @@ -535,39 +584,46 @@ func TestAbsentTracking(t *testing.T) { require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[0]).Status) require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[1]).Status) require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[2]).Status) - require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + require.True(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) - // when 2 pays 0, they both get noticed but addr[0] is not considered absent - vb := dl.fullBlock(&txntest.Txn{ + // when 2 pays 0, they both get noticed but addr[0] is not considered + // absent because it is a genesis account + vb = dl.fullBlock(&txntest.Txn{ Type: "pay", Sender: addrs[2], Receiver: addrs[0], Amount: 0, - }) - if ver >= checkingBegins { - require.Equal(t, vb.Block().AbsentParticipationAccounts, []basics.Address{addrs[2]}) - } + }) // #50 + require.Equal(t, vb.Block().AbsentParticipationAccounts, []basics.Address{addrs[2]}) + + twoPaysZero := vb.Block().Round() + require.EqualValues(t, 50, twoPaysZero) // addr[0] has never proposed or heartbeat so it is not considered absent require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[0]).Status) // addr[1] still hasn't been "noticed" require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[1]).Status) - require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[2]).Status == basics.Offline) + require.Equal(t, basics.Offline, lookup(t, dl.generator, addrs[2]).Status) require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + // separate the payments by a few blocks so it will be easier to test + // when the changes go into effect + for i := 0; i < 4; i++ { + dl.fullBlock() // #51-54 + } // now, when 2 pays 1, 1 gets suspended (unlike 0, we had 1 keyreg early on, so LastHeartbeat>0) vb = dl.fullBlock(&txntest.Txn{ Type: "pay", Sender: addrs[2], Receiver: addrs[1], Amount: 0, - }) - if ver >= checkingBegins { - require.Equal(t, vb.Block().AbsentParticipationAccounts, []basics.Address{addrs[1]}) - } + }) // #55 + twoPaysOne := vb.Block().Round() + require.EqualValues(t, 55, twoPaysOne) + require.Equal(t, vb.Block().AbsentParticipationAccounts, []basics.Address{addrs[1]}) require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[0]).Status) - require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[1]).Status == basics.Offline) + require.Equal(t, basics.Offline, lookup(t, dl.generator, addrs[1]).Status) require.False(t, lookup(t, dl.generator, addrs[1]).IncentiveEligible) - require.Equal(t, ver >= checkingBegins, lookup(t, dl.generator, addrs[2]).Status == basics.Offline) + require.Equal(t, basics.Offline, lookup(t, dl.generator, addrs[2]).Status) require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) // now, addrs[2] proposes, so it gets back online, but stays ineligible @@ -575,6 +631,49 @@ func TestAbsentTracking(t *testing.T) { dl.fullBlock() require.Equal(t, basics.Online, lookup(t, dl.generator, addrs[2]).Status) require.False(t, lookup(t, dl.generator, addrs[2]).IncentiveEligible) + + // "synchronize" so the loop below ends on 320 + for dl.fullBlock().Block().Round()%4 != 3 { + } + // keep in mind that each call to checkState also advances the round, so + // each loop advances by 4. + for rnd := dl.fullBlock().Block().Round(); rnd < 320; rnd = dl.fullBlock().Block().Round() { + // STILL nothing has changed, as we're under 320 + checkState(addrs[0], true, false, 833_333_333_333_333) + checkState(addrs[1], true, false, 833_333_333_333_333) + checkState(addrs[2], false, false, 0) + } + // rnd was 320 in the last fullBlock + + // We will soon see effects visible to `vote_params_get` + // In first block, addr[3] created an app. No effect on 0-2 + checkState(addrs[1], true, false, 833_333_333_333_333) // 321 + // in second block, the checkstate app was created + checkState(addrs[1], true, false, 833_333_333_333_333) // 322 + // addr[1] spent 10A on a fee in rnd 3, so online stake and eligibility adjusted in 323 + checkState(addrs[1], true, true, 833_333_323_333_333) // 323 + + for rnd := dl.fullBlock().Block().Round(); rnd < 320+twoEligible-1; rnd = dl.fullBlock().Block().Round() { + } + checkState(addrs[2], true, false, 833_333_333_429_333) + checkState(addrs[2], true, true, 833_333_331_429_333) // after keyreg w/ 2A is effective + + for rnd := dl.fullBlock().Block().Round(); rnd < 320+twoPaysZero-1; rnd = dl.fullBlock().Block().Round() { + } + // we're at the round before two's suspension kicks in + checkState(addrs[2], true, true, 833_333_331_429_333) // still "online" + checkState(addrs[0], true, false, 833_333_333_331_333) // paid fee in #5 and #11, we're at ~371 + // 2 was noticed & suspended after paying 0, eligible and amount go to 0 + checkState(addrs[2], false, false, 0) + checkState(addrs[0], true, false, 833_333_333_331_333) // addr 0 didn't get suspended (genesis) + + // roughly the same check, except for addr 1, which was genesis, but + // after doing a keyreg, became susceptible to suspension + for rnd := dl.fullBlock().Block().Round(); rnd < 320+twoPaysOne-1; rnd = dl.fullBlock().Block().Round() { + } + checkState(addrs[1], true, true, 833_333_323_230_333) // still online, balance irrelevant + // 1 was noticed & suspended after being paid by 2, so eligible and amount go to 0 + checkState(addrs[1], false, false, 0) }) } @@ -585,7 +684,9 @@ func TestAbsenteeChallenges(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis(func(cfg *ledgertesting.GenesisCfg) { - cfg.OnlineCount = 5 // Make online stake big, so these accounts won't be expected to propose + // Get some big accounts online, so the accounts we create here will be + // a tiny fraction, not expected to propose often. + cfg.OnlineCount = 5 }) checkingBegins := 40 @@ -703,6 +804,105 @@ func TestAbsenteeChallenges(t *testing.T) { }) } +// TestVoterAccess ensures that the `voter` opcode works properly when hooked up +// to a real ledger. +func TestVoterAccess(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis( + ledgertesting.TurnOffRewards, + func(cfg *ledgertesting.GenesisCfg) { + cfg.OnlineCount = 1 // So that one is online from the start + }) + getOnlineStake := `int 0; voter_params_get VoterBalance; itob; log; itob; log; online_stake; itob; log; int 1` + + // `voter_params_get` introduced in 40 + ledgertesting.TestConsensusRange(t, 40, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + stib := dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: getOnlineStake, + }) + stakeChecker := stib.ApplicationID + require.NotZero(t, stakeChecker) + + // have addrs[1] go online, though it won't be visible right away + dl.txn(&txntest.Txn{ + Type: "keyreg", + Sender: addrs[1], + VotePK: [32]byte{0xaa}, + SelectionPK: [32]byte{0xbb}, + }) + + one := basics.Address{0xaa, 0x11} + two := basics.Address{0xaa, 0x22} + three := basics.Address{0xaa, 0x33} + + checkState := func(addr basics.Address, online bool, expected uint64, total uint64) { + t.Helper() + if !online { + require.Zero(t, expected) + } + stib = dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addr, + ApplicationID: stakeChecker, + }) + logs := stib.ApplyData.EvalDelta.Logs + require.Len(t, logs, 3) + if online { + require.Equal(t, "\x00\x00\x00\x00\x00\x00\x00\x01", logs[0]) + require.Equal(t, int(expected), int(binary.BigEndian.Uint64([]byte(logs[1])))) + } else { + require.Equal(t, "\x00\x00\x00\x00\x00\x00\x00\x00", logs[0]) + require.Equal(t, int(expected), int(binary.BigEndian.Uint64([]byte(logs[1])))) + } + require.Equal(t, int(total), int(binary.BigEndian.Uint64([]byte(logs[2])))) + } + + checkState(addrs[0], true, 833_333_333_333_333, 833_333_333_333_333) + // checking again because addrs[0] just paid a fee, but we show online balance hasn't changed yet + checkState(addrs[0], true, 833_333_333_333_333, 833_333_333_333_333) + for i := 1; i < 10; i++ { + checkState(addrs[i], false, 0, 833_333_333_333_333) + } + + // Fund the new accounts and have them go online. + for i, addr := range []basics.Address{one, two, three} { + dl.txns(&txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: addr, + Amount: (uint64(i) + 1) * 1_000_000_000, + }, &txntest.Txn{ + Type: "keyreg", + Sender: addr, + VotePK: [32]byte{byte(i + 1)}, + SelectionPK: [32]byte{byte(i + 1)}, + }) + } + // they don't have online stake yet + for _, addr := range []basics.Address{one, two, three} { + checkState(addr, false, 0, 833_333_333_333_333) + } + for i := 0; i < 320; i++ { + dl.fullBlock() + } + // addr[1] is now visibly online. the total is across all five that are now online, minus various fees paid + checkState(addrs[1], true, 833_333_333_333_333-2000, 2*833_333_333_333_333-14000) + for i := 2; i < 10; i++ { // addrs[2-9] never came online + checkState(addrs[i], false, 0, 2*833_333_333_333_333-14000) + } + for i, addr := range []basics.Address{one, two, three} { + checkState(addr, true, (uint64(i)+1)*1_000_000_000-2000, 2*833_333_333_333_333-14000) + } + }) +} + // TestHoldingGet tests some of the corner cases for the asset_holding_get // opcode: the asset doesn't exist, the account doesn't exist, account not opted // in, vs it has none of the asset. This is tested here, even though it should diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go deleted file mode 100644 index f83dbb9809..0000000000 --- a/ledger/evalindexer.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (C) 2019-2024 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 ledger - -import ( - "errors" - "fmt" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto" - "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/ledger/eval" - "github.com/algorand/go-algorand/ledger/ledgercore" -) - -// A ledger interface that Indexer implements. This is a simplified version of the -// LedgerForEvaluator interface. Certain functions that the evaluator doesn't use -// in the trusting mode are excluded, and the present functions only request data -// at the latest round. However, functions below can be used for batch querying. -type indexerLedgerForEval interface { - LatestBlockHdr() (bookkeeping.BlockHeader, error) - // The value of the returned map is nil iff the account was not found. - LookupWithoutRewards(map[basics.Address]struct{}) (map[basics.Address]*ledgercore.AccountData, error) - // The returned map must have the same structure (elements) as the input map. - // If a resource is not found, it must be nil in `ledgercore.AccountResource`. - LookupResources(map[basics.Address]map[Creatable]struct{}) (map[basics.Address]map[Creatable]ledgercore.AccountResource, error) - GetAssetCreator(map[basics.AssetIndex]struct{}) (map[basics.AssetIndex]FoundAddress, error) - GetAppCreator(map[basics.AppIndex]struct{}) (map[basics.AppIndex]FoundAddress, error) - LatestTotals() (ledgercore.AccountTotals, error) - LookupKv(basics.Round, string) ([]byte, error) - - BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) -} - -// FoundAddress is a wrapper for an address and a boolean. -type FoundAddress struct { - Address basics.Address - Exists bool -} - -// EvalForIndexerResources contains resources preloaded from the Indexer database. -// Indexer is able to do the preloading more efficiently than the evaluator loading -// resources one by one. -type EvalForIndexerResources struct { - // The map value is nil iff the account does not exist. The account data is owned here. - Accounts map[basics.Address]*ledgercore.AccountData - Resources map[basics.Address]map[Creatable]ledgercore.AccountResource - Creators map[Creatable]FoundAddress -} - -// Creatable represent a single creatable object. -type Creatable struct { - Index basics.CreatableIndex - Type basics.CreatableType -} - -// Converter between indexerLedgerForEval and ledgerForEvaluator interfaces. -type indexerLedgerConnector struct { - il indexerLedgerForEval - genesisHash crypto.Digest - genesisProto config.ConsensusParams - latestRound basics.Round - roundResources EvalForIndexerResources -} - -func (l indexerLedgerConnector) FlushCaches() {} - -// BlockHdr is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) { - if round != l.latestRound { - return bookkeeping.BlockHeader{}, fmt.Errorf( - "BlockHdr() evaluator called this function for the wrong round %d, "+ - "latest round is %d", - round, l.latestRound) - } - return l.il.LatestBlockHdr() -} - -// CheckDup is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { - // This function is not used by evaluator. - return errors.New("CheckDup() not implemented") -} - -// LookupWithoutRewards is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) LookupWithoutRewards(round basics.Round, address basics.Address) (ledgercore.AccountData, basics.Round, error) { - // check to see if the account data in the cache. - if pad, has := l.roundResources.Accounts[address]; has { - if pad == nil { - return ledgercore.AccountData{}, round, nil - } - return *pad, round, nil - } - - accountDataMap, err := l.il.LookupWithoutRewards(map[basics.Address]struct{}{address: {}}) - if err != nil { - return ledgercore.AccountData{}, basics.Round(0), err - } - - accountData := accountDataMap[address] - if accountData == nil { - return ledgercore.AccountData{}, round, nil - } - return *accountData, round, nil -} - -func (l indexerLedgerConnector) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { - r, err := l.lookupResource(rnd, addr, basics.CreatableIndex(aidx), basics.AppCreatable) - return ledgercore.AppResource{AppParams: r.AppParams, AppLocalState: r.AppLocalState}, err -} - -func (l indexerLedgerConnector) LookupAsset(rnd basics.Round, addr basics.Address, aidx basics.AssetIndex) (ledgercore.AssetResource, error) { - r, err := l.lookupResource(rnd, addr, basics.CreatableIndex(aidx), basics.AssetCreatable) - return ledgercore.AssetResource{AssetParams: r.AssetParams, AssetHolding: r.AssetHolding}, err -} - -func (l indexerLedgerConnector) lookupResource(round basics.Round, address basics.Address, aidx basics.CreatableIndex, ctype basics.CreatableType) (ledgercore.AccountResource, error) { - // check to see if the account data in the cache. - if creatableMap, ok := l.roundResources.Resources[address]; ok { - if resource, ok := creatableMap[Creatable{aidx, ctype}]; ok { - return resource, nil - } - } - - accountResourceMap, err := - l.il.LookupResources(map[basics.Address]map[Creatable]struct{}{address: {{aidx, ctype}: {}}}) - if err != nil { - return ledgercore.AccountResource{}, err - } - - return accountResourceMap[address][Creatable{aidx, ctype}], nil -} - -// LookupKv delegates to the Ledger and marks the box key as touched for post-processing -func (l indexerLedgerConnector) LookupKv(rnd basics.Round, key string) ([]byte, error) { - value, err := l.il.LookupKv(rnd, key) - if err != nil { - return value, fmt.Errorf("LookupKv() in indexerLedgerConnector internal error: %w", err) - } - return value, nil -} - -// GetCreatorForRound is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) GetCreatorForRound(_ basics.Round, cindex basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { - var foundAddress FoundAddress - var has bool - // check to see if the account data in the cache. - if foundAddress, has = l.roundResources.Creators[Creatable{Index: cindex, Type: ctype}]; has { - return foundAddress.Address, foundAddress.Exists, nil - } - - switch ctype { - case basics.AssetCreatable: - foundAddresses, err := - l.il.GetAssetCreator(map[basics.AssetIndex]struct{}{basics.AssetIndex(cindex): {}}) - if err != nil { - return basics.Address{}, false, err - } - foundAddress = foundAddresses[basics.AssetIndex(cindex)] - case basics.AppCreatable: - foundAddresses, err := - l.il.GetAppCreator(map[basics.AppIndex]struct{}{basics.AppIndex(cindex): {}}) - if err != nil { - return basics.Address{}, false, err - } - foundAddress = foundAddresses[basics.AppIndex(cindex)] - default: - return basics.Address{}, false, fmt.Errorf("unknown creatable type %v", ctype) - } - - return foundAddress.Address, foundAddress.Exists, nil -} - -// GenesisHash is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) GenesisHash() crypto.Digest { - return l.genesisHash -} - -// GenesisProto is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) GenesisProto() config.ConsensusParams { - return l.genesisProto -} - -// Totals is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) LatestTotals() (rnd basics.Round, totals ledgercore.AccountTotals, err error) { - totals, err = l.il.LatestTotals() - rnd = l.latestRound - return -} - -// VotersForStateProof is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) VotersForStateProof(_ basics.Round) (*ledgercore.VotersForRound, error) { - // This function is not used by evaluator. - return nil, errors.New("VotersForStateProof() not implemented") -} - -// GetStateProofVerificationContext is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { - return nil, errors.New("GetStateProofVerificationContext() not implemented") -} - -func makeIndexerLedgerConnector(il indexerLedgerForEval, genesisHash crypto.Digest, genesisProto config.ConsensusParams, latestRound basics.Round, roundResources EvalForIndexerResources) indexerLedgerConnector { - return indexerLedgerConnector{ - il: il, - genesisHash: genesisHash, - genesisProto: genesisProto, - latestRound: latestRound, - roundResources: roundResources, - } -} - -// EvalForIndexer evaluates a block without validation using the given `proto`. -// Return the state delta and transactions with modified apply data according to `proto`. -// This function is used by Indexer which modifies `proto` to retrieve the asset -// close amount for each transaction even when the real consensus parameters do not -// support it. -func EvalForIndexer(il indexerLedgerForEval, block *bookkeeping.Block, proto config.ConsensusParams, resources EvalForIndexerResources) (ledgercore.StateDelta, []transactions.SignedTxnInBlock, error) { - ilc := makeIndexerLedgerConnector(il, block.GenesisHash(), proto, block.Round()-1, resources) - - eval, err := eval.StartEvaluator( - ilc, block.BlockHeader, - eval.EvaluatorOptions{ - PaysetHint: len(block.Payset), - ProtoParams: &proto, - Generate: false, - Validate: false, - }) - if err != nil { - return ledgercore.StateDelta{}, []transactions.SignedTxnInBlock{}, - fmt.Errorf("EvalForIndexer() err: %w", err) - } - - return eval.ProcessBlockForIndexer(block) -} diff --git a/ledger/evalindexer_test.go b/ledger/evalindexer_test.go deleted file mode 100644 index c85d5f0275..0000000000 --- a/ledger/evalindexer_test.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright (C) 2019-2024 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 ledger - -import ( - "errors" - "fmt" - "math/rand" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto" - "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/ledgercore" - ledgertesting "github.com/algorand/go-algorand/ledger/testing" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/test/partitiontest" -) - -type indexerLedgerForEvalImpl struct { - l *Ledger - latestRound basics.Round -} - -func (il indexerLedgerForEvalImpl) LatestBlockHdr() (bookkeeping.BlockHeader, error) { - return il.l.BlockHdr(il.latestRound) -} - -func (il indexerLedgerForEvalImpl) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) { - return il.l.BlockHdr(round) -} - -// The value of the returned map is nil iff the account was not found. -func (il indexerLedgerForEvalImpl) LookupWithoutRewards(addresses map[basics.Address]struct{}) (map[basics.Address]*ledgercore.AccountData, error) { - res := make(map[basics.Address]*ledgercore.AccountData) - - for address := range addresses { - accountData, _, err := il.l.LookupWithoutRewards(il.latestRound, address) - if err != nil { - return nil, err - } - - if accountData.IsZero() { - res[address] = nil - } else { - accountDataCopy := new(ledgercore.AccountData) - *accountDataCopy = accountData - res[address] = accountDataCopy - } - } - - return res, nil -} - -// The value of the returned map is nil iff the account was not found. -func (il indexerLedgerForEvalImpl) LookupResources(addresses map[basics.Address]map[Creatable]struct{}) (map[basics.Address]map[Creatable]ledgercore.AccountResource, error) { - res := make(map[basics.Address]map[Creatable]ledgercore.AccountResource) - - var err error - for address, creatables := range addresses { - for creatable := range creatables { - c, ok := res[address] - if !ok { - c = make(map[Creatable]ledgercore.AccountResource) - res[address] = c - } - - c[creatable], err = - il.l.lookupResource(il.latestRound, address, creatable.Index, creatable.Type) - if err != nil { - return nil, err - } - } - } - - return res, nil -} - -func (il indexerLedgerForEvalImpl) GetAssetCreator(map[basics.AssetIndex]struct{}) (map[basics.AssetIndex]FoundAddress, error) { - // This function is unused. - return nil, errors.New("GetAssetCreator() not implemented") -} - -func (il indexerLedgerForEvalImpl) GetAppCreator(map[basics.AppIndex]struct{}) (map[basics.AppIndex]FoundAddress, error) { - // This function is unused. - return nil, errors.New("GetAppCreator() not implemented") -} - -func (il indexerLedgerForEvalImpl) LookupKv(basics.Round, string) ([]byte, error) { - // This function is unused. - return nil, errors.New("LookupKv() not implemented") -} - -func (il indexerLedgerForEvalImpl) LatestTotals() (totals ledgercore.AccountTotals, err error) { - _, totals, err = il.l.LatestTotals() - return -} - -// Test that overriding the consensus parameters effects the generated apply data. -func TestEvalForIndexerCustomProtocolParams(t *testing.T) { - partitiontest.PartitionTest(t) - - genesisBalances, addrs, _ := ledgertesting.NewTestGenesis() - - var genHash crypto.Digest - crypto.RandBytes(genHash[:]) - block, err := bookkeeping.MakeGenesisBlock(protocol.ConsensusV24, - genesisBalances, "test", genHash) - require.NoError(t, err) - - dbName := t.Name() - cfg := config.GetDefaultLocal() - cfg.Archival = true - l, err := OpenLedger(logging.Base(), dbName, true, ledgercore.InitState{ - Block: block, - Accounts: genesisBalances.Balances, - GenesisHash: genHash, - }, cfg) - require.NoError(t, err) - defer l.Close() - - const assetid basics.AssetIndex = 1 - proto := config.Consensus[protocol.ConsensusV24] - - block = bookkeeping.MakeBlock(block.BlockHeader) - - createTxn := txntest.Txn{ - Type: "acfg", - Sender: addrs[0], - GenesisHash: block.GenesisHash(), - AssetParams: basics.AssetParams{ - Total: 200, - Decimals: 0, - Manager: addrs[0], - Reserve: addrs[0], - Freeze: addrs[0], - Clawback: addrs[0], - }, - } - createTxn.FillDefaults(proto) - createStib, err := block.BlockHeader.EncodeSignedTxn( - createTxn.SignedTxn(), transactions.ApplyData{}) - require.NoError(t, err) - - optInTxn := txntest.Txn{ - Type: "axfer", - Sender: addrs[1], - GenesisHash: block.GenesisHash(), - XferAsset: assetid, - AssetAmount: 0, - AssetReceiver: addrs[1], - } - optInTxn.FillDefaults(proto) - optInStib, err := block.BlockHeader.EncodeSignedTxn( - optInTxn.SignedTxn(), transactions.ApplyData{}) - require.NoError(t, err) - - fundTxn := txntest.Txn{ - Type: "axfer", - Sender: addrs[0], - GenesisHash: block.GenesisHash(), - XferAsset: assetid, - AssetAmount: 100, - AssetReceiver: addrs[1], - } - fundTxn.FillDefaults(proto) - fundStib, err := block.BlockHeader.EncodeSignedTxn( - fundTxn.SignedTxn(), transactions.ApplyData{}) - require.NoError(t, err) - - optOutTxn := txntest.Txn{ - Type: "axfer", - Sender: addrs[1], - GenesisHash: block.GenesisHash(), - XferAsset: assetid, - AssetAmount: 30, - AssetReceiver: addrs[0], - AssetCloseTo: addrs[0], - } - optOutTxn.FillDefaults(proto) - optOutStib, err := block.BlockHeader.EncodeSignedTxn( - optOutTxn.SignedTxn(), transactions.ApplyData{}) - require.NoError(t, err) - - block.Payset = []transactions.SignedTxnInBlock{ - createStib, optInStib, fundStib, optOutStib, - } - - il := indexerLedgerForEvalImpl{ - l: l, - latestRound: 0, - } - proto.EnableAssetCloseAmount = true - _, modifiedTxns, err := EvalForIndexer(il, &block, proto, EvalForIndexerResources{}) - require.NoError(t, err) - - require.Equal(t, 4, len(modifiedTxns)) - assert.Equal(t, uint64(70), modifiedTxns[3].AssetClosingAmount) -} - -// TestEvalForIndexerForExpiredAccounts tests that the EvalForIndexer function will correctly mark accounts offline -func TestEvalForIndexerForExpiredAccounts(t *testing.T) { - partitiontest.PartitionTest(t) - - genesisBalances, addrs, _ := ledgertesting.NewTestGenesis() - - var genHash crypto.Digest - crypto.RandBytes(genHash[:]) - block, err := bookkeeping.MakeGenesisBlock(protocol.ConsensusFuture, - genesisBalances, "test", genHash) - require.NoError(t, err) - - dbName := t.Name() - cfg := config.GetDefaultLocal() - cfg.Archival = true - l, err := OpenLedger(logging.Base(), dbName, true, ledgercore.InitState{ - Block: block, - Accounts: genesisBalances.Balances, - GenesisHash: genHash, - }, cfg) - require.NoError(t, err) - defer l.Close() - - proto := config.Consensus[protocol.ConsensusFuture] - - block = bookkeeping.MakeBlock(block.BlockHeader) - - il := indexerLedgerForEvalImpl{ - l: l, - latestRound: 0, - } - - _, _, err = EvalForIndexer(il, &block, proto, EvalForIndexerResources{}) - require.NoError(t, err) - - badBlock := block - // First validate that bad block is fine if we dont touch it... - _, _, err = EvalForIndexer(il, &badBlock, proto, EvalForIndexerResources{}) - require.NoError(t, err) - - // Introduce an unknown address, but this time the Eval function is called with parameters that - // don't necessarily mean that this will cause an error. Just that an empty address will be added - badBlock.ExpiredParticipationAccounts = append(badBlock.ExpiredParticipationAccounts, basics.Address{123}) - - _, _, err = EvalForIndexer(il, &badBlock, proto, EvalForIndexerResources{}) - require.NoError(t, err) - - badBlock = block - - // Now we add way too many accounts which will cause resetExpiredOnlineAccountsParticipationKeys() to fail - addressToCopy := addrs[0] - - for i := 0; i < proto.MaxProposedExpiredOnlineAccounts+1; i++ { - badBlock.ExpiredParticipationAccounts = append(badBlock.ExpiredParticipationAccounts, addressToCopy) - } - - _, _, err = EvalForIndexer(il, &badBlock, proto, EvalForIndexerResources{}) - require.Error(t, err) - - // Sanity Check - - badBlock = block - - _, _, err = EvalForIndexer(il, &badBlock, proto, EvalForIndexerResources{}) - require.NoError(t, err) -} - -func newTestLedger(t testing.TB, balances bookkeeping.GenesisBalances) *Ledger { - var genHash crypto.Digest - crypto.RandBytes(genHash[:]) - genBlock, err := bookkeeping.MakeGenesisBlock(protocol.ConsensusFuture, balances, "test", genHash) - require.NoError(t, err) - require.False(t, genBlock.FeeSink.IsZero()) - require.False(t, genBlock.RewardsPool.IsZero()) - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - cfg := config.GetDefaultLocal() - cfg.Archival = true - l, err := OpenLedger(logging.Base(), dbName, true, ledgercore.InitState{ - Block: genBlock, - Accounts: balances.Balances, - GenesisHash: genHash, - }, cfg) - require.NoError(t, err) - return l -} - -// Test that preloading data in cow base works as expected. -func TestResourceCaching(t *testing.T) { - partitiontest.PartitionTest(t) - - var address basics.Address - _, err := rand.Read(address[:]) - require.NoError(t, err) - - creatable := Creatable{ - Index: basics.CreatableIndex(7), - Type: basics.AssetCreatable, - } - - genesisInitState, _, _ := ledgertesting.GenesisWithProto(10, protocol.ConsensusFuture) - - genesisBalances := bookkeeping.GenesisBalances{ - Balances: genesisInitState.Accounts, - FeeSink: testSinkAddr, - RewardsPool: testPoolAddr, - Timestamp: 0, - } - l := newTestLedger(t, genesisBalances) - defer l.Close() - - genesisBlockHeader, err := l.BlockHdr(basics.Round(0)) - require.NoError(t, err) - block := bookkeeping.MakeBlock(genesisBlockHeader) - - resources := EvalForIndexerResources{ - Accounts: map[basics.Address]*ledgercore.AccountData{ - address: { - AccountBaseData: ledgercore.AccountBaseData{ - MicroAlgos: basics.MicroAlgos{Raw: 5}, - }, - }, - }, - Resources: map[basics.Address]map[Creatable]ledgercore.AccountResource{ - address: { - creatable: { - AssetParams: &basics.AssetParams{ - Total: 8, - }, - AssetHolding: &basics.AssetHolding{ - Amount: 9, - }, - }, - }, - }, - Creators: map[Creatable]FoundAddress{ - {Index: basics.CreatableIndex(6), Type: basics.AssetCreatable}: {Address: address, Exists: true}, - {Index: basics.CreatableIndex(6), Type: basics.AppCreatable}: {Address: address, Exists: false}, - }, - } - - proto := config.Consensus[protocol.ConsensusFuture] - ilc := makeIndexerLedgerConnector(indexerLedgerForEvalImpl{l: l, latestRound: basics.Round(0)}, block.GenesisHash(), proto, block.Round()-1, resources) - - { - accountData, rnd, err := ilc.LookupWithoutRewards(basics.Round(0), address) - require.NoError(t, err) - assert.Equal(t, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{MicroAlgos: basics.MicroAlgos{Raw: 5}}}, accountData) - assert.Equal(t, basics.Round(0), rnd) - } - { - accountResource, err := ilc.LookupAsset( - basics.Round(0), address, basics.AssetIndex(7)) - require.NoError(t, err) - expected := ledgercore.AssetResource{ - AssetParams: &basics.AssetParams{ - Total: 8, - }, - AssetHolding: &basics.AssetHolding{ - Amount: 9, - }, - } - assert.Equal(t, expected, accountResource) - } - { - address, found, err := ilc.GetCreatorForRound(basics.Round(0), basics.CreatableIndex(6), basics.AssetCreatable) - require.NoError(t, err) - require.True(t, found) - assert.Equal(t, address, address) - } - { - _, found, err := ilc.GetCreatorForRound(basics.Round(0), basics.CreatableIndex(6), basics.AppCreatable) - require.NoError(t, err) - require.False(t, found) - } -} diff --git a/ledger/tracker.go b/ledger/tracker.go index 6d976a9763..d99f997ebe 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -908,6 +908,14 @@ func (aul *accountUpdatesLedgerEvaluator) LookupWithoutRewards(rnd basics.Round, return data, validThrough, err } +func (aul *accountUpdatesLedgerEvaluator) LookupAgreement(rnd basics.Round, addr basics.Address) (basics.OnlineAccountData, error) { + return aul.ao.LookupOnlineAccountData(rnd, addr) +} + +func (aul *accountUpdatesLedgerEvaluator) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) { + return aul.ao.onlineCirculation(rnd, voteRnd) +} + func (aul *accountUpdatesLedgerEvaluator) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) { r, _, err := aul.au.lookupResource(rnd, addr, basics.CreatableIndex(aidx), basics.AppCreatable, false /* don't sync */) return ledgercore.AppResource{AppParams: r.AppParams, AppLocalState: r.AppLocalState}, err From 51dc0b4a8cc18513a692318abe9b37750c0fda6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 13:57:00 -0400 Subject: [PATCH 127/218] Build: bump jinja2 from 3.1.3 to 3.1.4 in /test/heapwatch (#5991) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/heapwatch/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt index 9743ce85c1..d4d68874dd 100644 --- a/test/heapwatch/requirements.txt +++ b/test/heapwatch/requirements.txt @@ -1,6 +1,6 @@ dash==2.15.0 dash-table==5.0.0 -Jinja2==3.1.3 +Jinja2==3.1.4 matplotlib==3.7.2 plotly==5.16.0 py-algorand-sdk==2.3.0 From f75fe9eea721f12eeb030aa1f34a418067eae839 Mon Sep 17 00:00:00 2001 From: tianzedavid <168427849+tianzedavid@users.noreply.github.com> Date: Thu, 16 May 2024 02:57:43 +0800 Subject: [PATCH 128/218] chore: fix non-exported function names in comments (#5987) --- agreement/proposalManager.go | 2 +- catchup/catchpointService.go | 2 +- catchup/universalFetcher.go | 2 +- cmd/goal/application.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/agreement/proposalManager.go b/agreement/proposalManager.go index 6ec8d2e1b6..472e8bb975 100644 --- a/agreement/proposalManager.go +++ b/agreement/proposalManager.go @@ -245,7 +245,7 @@ func (m *proposalManager) handleMessageEvent(r routerHandle, p player, e filtera } } -// filterVote filters a vote, checking if it is both fresh and not a duplicate, returning +// filterProposalVote filters a vote, checking if it is both fresh and not a duplicate, returning // an errProposalManagerPVNotFresh or errProposalManagerPVDuplicate if so, else nil. // It also returns a bool indicating whether this proposal-vote should still be verified for tracking credential history. func (m *proposalManager) filterProposalVote(p player, r routerHandle, uv unauthenticatedVote, freshData freshnessData) (bool, error) { diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 3954c1cd6c..974f5964f1 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -698,7 +698,7 @@ func (cs *CatchpointCatchupService) fetchBlock(round basics.Round, retryCount ui return blk, cert, downloadDuration, psp, false, nil } -// processStageLedgerDownload is the fifth catchpoint catchup stage. It completes the catchup process, swap the new tables and restart the node functionality. +// processStageSwitch is the fifth catchpoint catchup stage. It completes the catchup process, swap the new tables and restart the node functionality. func (cs *CatchpointCatchupService) processStageSwitch() (err error) { err = cs.ledgerAccessor.CompleteCatchup(cs.ctx) if err != nil { diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index 926c85bb48..31d82adc98 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -43,7 +43,7 @@ type universalBlockFetcher struct { log logging.Logger } -// makeUniversalFetcher returns a fetcher for http and ws peers. +// makeUniversalBlockFetcher returns a fetcher for http and ws peers. func makeUniversalBlockFetcher(log logging.Logger, net network.GossipNode, config config.Local) *universalBlockFetcher { return &universalBlockFetcher{ config: config, diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 922540b726..0a42f65b5d 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -1179,7 +1179,7 @@ const maxAppArgs = 16 // minus 1 for the final app argument becoming a tuple of the remaining method args const methodArgsTupleThreshold = maxAppArgs - 2 -// parseArgJSONtoByteSlice convert input method arguments to ABI encoded bytes +// parseMethodArgJSONtoByteSlice convert input method arguments to ABI encoded bytes // it converts funcArgTypes into a tuple type and apply changes over input argument string (in JSON format) // if there are greater or equal to 15 inputs, then we compact the tailing inputs into one tuple func parseMethodArgJSONtoByteSlice(argTypes []string, jsonArgs []string, applicationArgs *[][]byte) error { From 981abad60d1fdd2993847434417982b4d373b7b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 11:55:00 -0400 Subject: [PATCH 129/218] Build: bump golang.org/x/net from 0.17.0 to 0.23.0 (#5979) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gary Malouf <982483+gmalouf@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- tools/block-generator/go.mod | 6 +++--- tools/block-generator/go.sum | 12 ++++++------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index c15195b54b..a32da9da8c 100644 --- a/go.mod +++ b/go.mod @@ -42,10 +42,10 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.19.0 + golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a golang.org/x/sync v0.6.0 - golang.org/x/sys v0.17.0 + golang.org/x/sys v0.18.0 golang.org/x/text v0.14.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 pgregory.net/rapid v0.6.2 @@ -151,8 +151,8 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/mod v0.15.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index badf55816d..cdae303d5b 100644 --- a/go.sum +++ b/go.sum @@ -591,8 +591,8 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh 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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= @@ -632,8 +632,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v 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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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= @@ -683,11 +683,11 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 29bdbdb4e8..cfeb442668 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -125,12 +125,12 @@ require ( go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect golang.org/x/mod v0.15.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.18.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index e34dae4485..dbd7cdeb72 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -541,8 +541,8 @@ golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPh 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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= @@ -582,8 +582,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v 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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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= @@ -629,8 +629,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 2335b3541202216b1f048aee0f0ea4c3e40e2121 Mon Sep 17 00:00:00 2001 From: algo-devops-service <80971703+Algo-devops-service@users.noreply.github.com> Date: Mon, 20 May 2024 12:03:09 -0400 Subject: [PATCH 130/218] CICD: go-algorand relstable3.24.0-remerge mergeback (#6002) Co-authored-by: John Lee <64482439+algojohnlee@users.noreply.github.com> --- config/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/version.go b/config/version.go index 643ac34e86..8716fbcdcb 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 24 +const VersionMinor = 25 // Version is the type holding our full version information. type Version struct { From 2f852b78d7e4bf7e679a2ce91d1ca45fa6f0fd9c Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 30 May 2024 11:11:47 -0400 Subject: [PATCH 131/218] Tests: Fix flakey ECDSA testing (#6005) --- data/transactions/logic/crypto_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/transactions/logic/crypto_test.go b/data/transactions/logic/crypto_test.go index 5aad230289..0ba695dce5 100644 --- a/data/transactions/logic/crypto_test.go +++ b/data/transactions/logic/crypto_test.go @@ -606,8 +606,8 @@ ecdsa_verify Secp256r1 ri, si, err := ecdsa.Sign(rand.Reader, key, msg[:]) require.NoError(t, err) - r := ri.Bytes() - s := si.Bytes() + r := ri.FillBytes(make([]byte, 32)) + s := si.FillBytes(make([]byte, 32)) rTampered := slices.Clone(r) rTampered[0] += byte(1) // intentional overflow @@ -826,8 +826,8 @@ func benchmarkEcdsaGenData(b *testing.B, curve EcdsaCurve) (data []benchmarkEcds } else if curve == Secp256r1 { r, s, err := ecdsa.Sign(rand.Reader, key, data[i].msg[:]) require.NoError(b, err) - data[i].r = r.Bytes() - data[i].s = s.Bytes() + data[i].r = r.FillBytes(make([]byte, 32)) + data[i].s = s.FillBytes(make([]byte, 32)) } } return data From f6338578d31efa2537924446b266a8b5a3770248 Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 30 May 2024 13:19:19 -0400 Subject: [PATCH 132/218] CICD: Update to switch e2e_expect to large (#6004) --- .circleci/config.yml | 4 ++-- CODEOWNERS | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 CODEOWNERS diff --git a/.circleci/config.yml b/.circleci/config.yml index b9ac9f3faf..8208fe063b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -303,7 +303,7 @@ jobs: parameters: platform: type: string - executor: << parameters.platform >>_medium + executor: << parameters.platform >>_large working_directory: << pipeline.parameters.build_dir >>/project parallelism: 10 environment: @@ -319,7 +319,7 @@ jobs: parameters: platform: type: string - executor: << parameters.platform >>_medium + executor: << parameters.platform >>_large working_directory: << pipeline.parameters.build_dir >>/project parallelism: 2 environment: diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 3c88c6e71e..0000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -.github/ @algorand/devops -.circleci/ @algorand/devops From 0f1b10badc14481a0319e805b57b49764163bb05 Mon Sep 17 00:00:00 2001 From: Michael T Chuang Date: Tue, 4 Jun 2024 07:21:46 -0700 Subject: [PATCH 133/218] ci: upgrade github checkout action to v4 (#6010) --- .github/workflows/benchmarks.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/codegen_verification.yml | 2 +- .github/workflows/container.yml | 4 ++-- .github/workflows/reviewdog.yml | 4 ++-- .github/workflows/tools.yml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 5e11c6cac9..bd7c93f7e5 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -17,7 +17,7 @@ jobs: name: Performance regression check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.5.3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4.0.1 with: go-version-file: 'go.mod' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 785304b0db..0c08e8abbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: update: true path-type: inherit - name: Check out code - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Determine Go version diff --git a/.github/workflows/codegen_verification.yml b/.github/workflows/codegen_verification.yml index a01e3e5e9c..ad90754d10 100644 --- a/.github/workflows/codegen_verification.yml +++ b/.github/workflows/codegen_verification.yml @@ -14,7 +14,7 @@ jobs: - 8080:8080 steps: - name: Check out code - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4 with: fetch-depth: 0 path: go-algorand diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 75134a6d6a..074196e01a 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4 - name: Generate Container Metadata id: meta @@ -59,7 +59,7 @@ jobs: # if: github.ref == format('refs/heads/{0}', 'master') # steps: # - name: Checkout Code - # uses: actions/checkout@v3.5.3 + # uses: actions/checkout@v4 # - name: Update DockerHub Repository Description # uses: peter-evans/dockerhub-description@v3 diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index efa0d75217..1eff6ac578 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4 with: fetch-depth: 0 # required for new-from-rev option in .golangci.yml # move go out of the way temporarily to avoid "go list ./..." from installing modules @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4 with: fetch-depth: 0 # required for new-from-rev option in .golangci.yml # move go out of the way temporarily to avoid "go list ./..." from installing modules diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 064f100f4a..95acb22474 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code into the Go module directory - uses: actions/checkout@v3.5.3 + uses: actions/checkout@v4 # move go out of the way temporarily to avoid "go list ./..." from installing modules - name: Make libsodium.a run: sudo mv /usr/bin/go /usr/bin/go.bak && make crypto/libs/linux/amd64/lib/libsodium.a && sudo mv /usr/bin/go.bak /usr/bin/go From 8d228c0445cd7e744e24a28c90f05915713b66cd Mon Sep 17 00:00:00 2001 From: John Lee Date: Wed, 5 Jun 2024 11:27:24 -0400 Subject: [PATCH 134/218] CICD: update OS support in test release script (#6012) --- test/packages/test_release.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/packages/test_release.sh b/test/packages/test_release.sh index 1a66184eb2..e92f52e459 100755 --- a/test/packages/test_release.sh +++ b/test/packages/test_release.sh @@ -15,11 +15,12 @@ then fi OS_LIST=( - centos:7 - quay.io/centos/centos:stream8 - fedora:38 + quay.io/centos/centos:stream9 + fedora:39 + fedora:40 ubuntu:20.04 ubuntu:22.04 + ubuntu:24.04 ) BUCKET=algorand-builds @@ -68,7 +69,8 @@ EOF then WITH_PACMAN=$(echo -e "${TOKENIZED//\{\{PACMAN\}\}/RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y curl}") else - WITH_PACMAN=$(echo -e "${TOKENIZED//\{\{PACMAN\}\}/RUN yum install -y curl}") + # fedora and centos already have curl installed. + WITH_PACMAN=$(echo -e "${TOKENIZED//\{\{PACMAN\}\}/}") fi echo -e "$BLUE_FG[$0]$END_FG_COLOR Testing $item..." From 8d9632c785d93af87c0b0ddab81cd3885d687bc5 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 7 Jun 2024 10:15:56 -0400 Subject: [PATCH 135/218] Tests: Fix e2e kmd timeout (#6016) --- test/scripts/e2e_client_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/e2e_client_runner.py b/test/scripts/e2e_client_runner.py index 3d5888696f..2ff7f876cc 100755 --- a/test/scripts/e2e_client_runner.py +++ b/test/scripts/e2e_client_runner.py @@ -106,7 +106,7 @@ def _script_thread_inner(runset, scriptname, timeout): # create a wallet for the test walletname = base64.b16encode(os.urandom(16)).decode() - winfo = kmd.create_wallet(walletname, '') + winfo = kmd.create_wallet(walletname, '', timeout=120) # 2 minute timeout handle = kmd.init_wallet_handle(winfo['id'], '') addr = kmd.generate_key(handle) From d1e7725e95275fb04f8c58b010470ce410462fdb Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Fri, 7 Jun 2024 10:42:43 -0400 Subject: [PATCH 136/218] metrics: Add TagCounterFiltered metrics for handler calls (#6013) --- network/wsNetwork.go | 2 ++ network/wsPeer.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 295bd71155..9c203d8839 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -1147,6 +1147,8 @@ func (wn *msgHandler) messageHandlerThread(wg *sync.WaitGroup, peersConnectivity networkIncomingBufferMicros.AddUint64(uint64(bufferNanos/1000), nil) handleTime := handled.Sub(start) networkHandleMicros.AddUint64(uint64(handleTime.Nanoseconds()/1000), nil) + networkHandleMicrosByTag.Add(string(msg.Tag), uint64(handleTime.Nanoseconds()/1000)) + networkHandleCountByTag.Add(string(msg.Tag), 1) switch outmsg.Action { case Disconnect: wg.Add(1) diff --git a/network/wsPeer.go b/network/wsPeer.go index adf211f592..c4b64bdec4 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -63,6 +63,8 @@ func init() { networkReceivedBytesByTag = metrics.NewTagCounterFiltered("algod_network_received_bytes_{TAG}", "Number of bytes that were received from the network for {TAG} messages", tagStringList, "UNK") networkMessageReceivedByTag = metrics.NewTagCounterFiltered("algod_network_message_received_{TAG}", "Number of complete messages that were received from the network for {TAG} messages", tagStringList, "UNK") networkMessageSentByTag = metrics.NewTagCounterFiltered("algod_network_message_sent_{TAG}", "Number of complete messages that were sent to the network for {TAG} messages", tagStringList, "UNK") + networkHandleCountByTag = metrics.NewTagCounterFiltered("algod_network_rx_handle_countbytag_{TAG}", "count of handler calls in the receive thread for {TAG} messages", tagStringList, "UNK") + networkHandleMicrosByTag = metrics.NewTagCounterFiltered("algod_network_rx_handle_microsbytag_{TAG}", "microseconds spent by protocol handlers in the receive thread for {TAG} messages", tagStringList, "UNK") matched := false for _, version := range SupportedProtocolVersions { @@ -91,6 +93,9 @@ var networkMessageReceivedByTag *metrics.TagCounter var networkMessageSentTotal = metrics.MakeCounter(metrics.NetworkMessageSentTotal) var networkMessageSentByTag *metrics.TagCounter +var networkHandleMicrosByTag *metrics.TagCounter +var networkHandleCountByTag *metrics.TagCounter + var networkConnectionsDroppedTotal = metrics.MakeCounter(metrics.NetworkConnectionsDroppedTotal) var networkMessageQueueMicrosTotal = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_message_sent_queue_micros_total", Description: "Total microseconds message spent waiting in queue to be sent"}) From 0c02f9b2ff07955d3048e0b6fa2335abaca87c32 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 7 Jun 2024 11:20:15 -0400 Subject: [PATCH 137/218] Tests: Fix flakey htlc test (#6017) --- test/scripts/e2e_subs/htlc-teal-test.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/scripts/e2e_subs/htlc-teal-test.sh b/test/scripts/e2e_subs/htlc-teal-test.sh index d2b70c533d..617e51d5cc 100755 --- a/test/scripts/e2e_subs/htlc-teal-test.sh +++ b/test/scripts/e2e_subs/htlc-teal-test.sh @@ -46,8 +46,9 @@ ${gcmd} clerk send --fee=1000 --from-program ${TEMPDIR}/atomic.teal -a=0 -t=${ZE # Check balance BALANCEB=$(${gcmd} account balance -a ${ACCOUNTB} | awk '{ print $1 }') -if [ $BALANCEB -ne 9999000 ]; then - date "+htlc-teal-test FAIL wanted balance=9999000 but got ${BALANCEB} %Y%m%d_%H%M%S" +# Use >= 9999000 to account for rewards which may have accumulated +if [ $BALANCEB -lt 9999000 ]; then + date "+htlc-teal-test FAIL wanted balance>=9999000 but got ${BALANCEB} %Y%m%d_%H%M%S" false fi From f851f50164ae771c8af5841dceb9fe57fedfe96d Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:26:55 -0400 Subject: [PATCH 138/218] ledger: introduce expired stake cache (#6014) --- ledger/acctonline.go | 33 +++++++++++---- ledger/acctonline_expired_test.go | 41 +++++++++--------- ledger/acctonline_test.go | 6 +-- ledger/acctonlineexp.go | 66 +++++++++++++++++++++++++++++ ledger/acctonlineexp_test.go | 69 +++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 ledger/acctonlineexp.go create mode 100644 ledger/acctonlineexp_test.go diff --git a/ledger/acctonline.go b/ledger/acctonline.go index 667255fb51..0db04e92ad 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -118,6 +118,9 @@ type onlineAccounts struct { // disableCache (de)activates the LRU cache use in onlineAccounts disableCache bool + + // cache for expired online circulation stake since the underlying query is quite heavy + expiredCirculationCache *expiredCirculationCache } // initialize initializes the accountUpdates structure @@ -125,6 +128,9 @@ func (ao *onlineAccounts) initialize(cfg config.Local) { ao.accountsReadCond = sync.NewCond(ao.accountsMu.RLocker()) ao.acctLookback = cfg.MaxAcctLookback ao.disableCache = cfg.DisableLedgerLRUCache + // 2 pages * 256 entries look large enough to handle + // both early and late votes, and well as a current and previous stateproof periods + ao.expiredCirculationCache = makeExpiredCirculationCache(256) } // loadFromDisk is the 2nd level initialization, and is required before the onlineAccounts becomes functional @@ -549,7 +555,7 @@ func (ao *onlineAccounts) onlineCirculation(rnd basics.Round, voteRnd basics.Rou if rnd == 0 { return totalStake, nil } - expiredStake, err := ao.ExpiredOnlineCirculation(rnd, voteRnd) + expiredStake, err := ao.expiredOnlineCirculation(rnd, voteRnd) if err != nil { return basics.MicroAlgos{}, err } @@ -874,7 +880,7 @@ func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Rou for uint64(len(candidates)) < n+uint64(len(modifiedAccounts)) { var accts map[basics.Address]*ledgercore.OnlineAccount start := time.Now() - ledgerAccountsonlinetopCount.Inc(nil) + ledgerAccountsOnlineTopCount.Inc(nil) err = ao.dbs.Snapshot(func(ctx context.Context, tx trackerdb.SnapshotScope) (err error) { ar, err := tx.MakeAccountsReader() if err != nil { @@ -888,7 +894,7 @@ func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Rou dbRound, err = ar.AccountsRound() return }) - ledgerAccountsonlinetopMicros.AddMicrosecondsSince(start, nil) + ledgerAccountsOnlineTopMicros.AddMicrosecondsSince(start, nil) if err != nil { return nil, basics.MicroAlgos{}, err } @@ -965,7 +971,7 @@ func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Rou // If set, return total online stake minus all future expired stake by voteRnd if params.ExcludeExpiredCirculation { - expiredStake, err := ao.ExpiredOnlineCirculation(rnd, voteRnd) + expiredStake, err := ao.expiredOnlineCirculation(rnd, voteRnd) if err != nil { return nil, basics.MicroAlgos{}, err } @@ -1027,6 +1033,9 @@ func (ao *onlineAccounts) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) ( rewardsParams := config.Consensus[roundParams.CurrentProtocol] rewardsLevel := roundParams.RewardsLevel + start := time.Now() + ledgerAccountExpiredByRoundCount.Inc(nil) + // Step 1: get all online accounts from DB for rnd // Not unlocking ao.accountsMu yet, to stay consistent with Step 2 var dbRound basics.Round @@ -1042,6 +1051,7 @@ func (ao *onlineAccounts) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) ( dbRound, err = ar.AccountsRound() return err }) + ledgerAccountsExpiredByRoundMicros.AddMicrosecondsSince(start, nil) if err != nil { return nil, err } @@ -1086,9 +1096,13 @@ func (ao *onlineAccounts) onlineAcctsExpiredByRound(rnd, voteRnd basics.Round) ( return expiredAccounts, nil } -// ExpiredOnlineCirculation returns the total online stake for accounts with participation keys registered +// expiredOnlineCirculation returns the total online stake for accounts with participation keys registered // at round `rnd` that are expired by round `voteRnd`. -func (ao *onlineAccounts) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { +func (ao *onlineAccounts) expiredOnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { + if expiredStake, ok := ao.expiredCirculationCache.get(rnd, voteRnd); ok { + return expiredStake, nil + } + expiredAccounts, err := ao.onlineAcctsExpiredByRound(rnd, voteRnd) if err != nil { return basics.MicroAlgos{}, err @@ -1101,8 +1115,11 @@ func (ao *onlineAccounts) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) (b return basics.MicroAlgos{}, fmt.Errorf("ExpiredOnlineCirculation: overflow totaling expired stake") } } + ao.expiredCirculationCache.put(rnd, voteRnd, expiredStake) return expiredStake, nil } -var ledgerAccountsonlinetopCount = metrics.NewCounter("ledger_accountsonlinetop_count", "calls") -var ledgerAccountsonlinetopMicros = metrics.NewCounter("ledger_accountsonlinetop_micros", "µs spent") +var ledgerAccountsOnlineTopCount = metrics.NewCounter("ledger_accountsonlinetop_count", "calls") +var ledgerAccountsOnlineTopMicros = metrics.NewCounter("ledger_accountsonlinetop_micros", "µs spent") +var ledgerAccountExpiredByRoundCount = metrics.NewCounter("ledger_accountsexpired_count", "calls") +var ledgerAccountsExpiredByRoundMicros = metrics.NewCounter("ledger_accountsexpired_micros", "µs spent") diff --git a/ledger/acctonline_expired_test.go b/ledger/acctonline_expired_test.go index e51494d184..25bfe3b11b 100644 --- a/ledger/acctonline_expired_test.go +++ b/ledger/acctonline_expired_test.go @@ -49,7 +49,7 @@ type onlineAcctModel interface { LookupAgreement(rnd basics.Round, addr basics.Address) onlineAcctModelAcct OnlineCirculation(rnd basics.Round, voteRnd basics.Round) basics.MicroAlgos - ExpiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos + expiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos } // mapOnlineAcctModel provides a reference implementation for tracking online accounts used @@ -133,7 +133,7 @@ func (m *mapOnlineAcctModel) OnlineCirculation(rnd basics.Round, voteRnd basics. return m.sumAcctStake(accts) } -func (m *mapOnlineAcctModel) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos { +func (m *mapOnlineAcctModel) expiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos { accts := m.onlineAcctsExpiredByRound(rnd, voteRnd) return m.sumAcctStake(accts) } @@ -385,7 +385,7 @@ func (m *doubleLedgerAcctModel) OnlineCirculation(rnd basics.Round, voteRnd basi // has already subtracted the expired stake. So to get the total, add // it back in by querying ExpiredOnlineCirculation. if m.params.ExcludeExpiredCirculation { - expiredStake := m.ExpiredOnlineCirculation(rnd, rnd+320) + expiredStake := m.expiredOnlineCirculation(rnd, rnd+320) valStake = m.ops.Add(valStake, expiredStake) } @@ -404,20 +404,26 @@ func (l *Ledger) OnlineTotalStake(rnd basics.Round) (basics.MicroAlgos, error) { return totalStake, err } -// ExpiredOnlineCirculation is a wrapper to call onlineAccounts.ExpiredOnlineCirculation safely. -func (l *Ledger) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { +// expiredOnlineCirculation is a wrapper to call onlineAccounts.expiredOnlineCirculation safely. +func (l *Ledger) expiredOnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { l.trackerMu.RLock() defer l.trackerMu.RUnlock() - return l.acctsOnline.ExpiredOnlineCirculation(rnd, voteRnd) + return l.acctsOnline.expiredOnlineCirculation(rnd, voteRnd) } -// ExpiredOnlineCirculation returns the total expired stake at rnd this model produced, while +// expiredOnlineCirculation returns the total expired stake at rnd this model produced, while // also asserting that the validator and generator Ledgers both agree. -func (m *doubleLedgerAcctModel) ExpiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos { - valStake, err := m.dl.validator.ExpiredOnlineCirculation(rnd, voteRnd) +func (m *doubleLedgerAcctModel) expiredOnlineCirculation(rnd, voteRnd basics.Round) basics.MicroAlgos { + valStake, err := m.dl.validator.expiredOnlineCirculation(rnd, voteRnd) require.NoError(m.t, err) - genStake, err := m.dl.generator.ExpiredOnlineCirculation(rnd, voteRnd) + valCachedStake, has := m.dl.validator.acctsOnline.expiredCirculationCache.get(rnd, voteRnd) + require.True(m.t, has) + require.Equal(m.t, valStake, valCachedStake) + genStake, err := m.dl.generator.expiredOnlineCirculation(rnd, voteRnd) require.NoError(m.t, err) + genCachedStake, has := m.dl.generator.acctsOnline.expiredCirculationCache.get(rnd, voteRnd) + require.True(m.t, has) + require.Equal(m.t, genStake, genCachedStake) require.Equal(m.t, valStake, genStake) return valStake } @@ -483,7 +489,7 @@ func testOnlineAcctModelSimple(t *testing.T, m onlineAcctModel) { a.Equal(basics.MicroAlgos{Raw: 43_210_000}, onlineStake) // expired stake is acct 2 + acct 4 - expiredStake := m.ExpiredOnlineCirculation(680, 1000) + expiredStake := m.expiredOnlineCirculation(680, 1000) a.Equal(basics.MicroAlgos{Raw: 22_110_000}, expiredStake) } @@ -519,15 +525,6 @@ type goOfflineAction struct{ addr basics.Address } func (a goOfflineAction) apply(t *testing.T, m onlineAcctModel) { m.goOffline(a.addr) } -type updateStakeAction struct { - addr basics.Address - stake uint64 -} - -func (a updateStakeAction) apply(t *testing.T, m onlineAcctModel) { - m.updateStake(a.addr, basics.MicroAlgos{Raw: a.stake}) -} - type checkOnlineStakeAction struct { rnd, voteRnd basics.Round online, expired uint64 @@ -535,7 +532,7 @@ type checkOnlineStakeAction struct { func (a checkOnlineStakeAction) apply(t *testing.T, m onlineAcctModel) { onlineStake := m.OnlineCirculation(a.rnd, a.voteRnd) - expiredStake := m.ExpiredOnlineCirculation(a.rnd, a.voteRnd) + expiredStake := m.expiredOnlineCirculation(a.rnd, a.voteRnd) require.Equal(t, basics.MicroAlgos{Raw: a.online}, onlineStake, "round %d, cur %d", a.rnd, m.currentRound()) require.Equal(t, basics.MicroAlgos{Raw: a.expired}, expiredStake, "rnd %d voteRnd %d, cur %d", a.rnd, a.voteRnd, m.currentRound()) } @@ -681,7 +678,7 @@ func BenchmarkExpiredOnlineCirculation(b *testing.B) { // query expired circulation across the available range (last 320 rounds, from ~680 to ~1000) startRnd := m.currentRound() - 320 offset := basics.Round(i % 320) - _, err := m.dl.validator.ExpiredOnlineCirculation(startRnd+offset, startRnd+offset+320) + _, err := m.dl.validator.expiredOnlineCirculation(startRnd+offset, startRnd+offset+320) require.NoError(b, err) //total, err := m.dl.validator.OnlineTotalStake(startRnd + offset) //b.Log("expired circulation", startRnd+offset, startRnd+offset+320, "returned", expiredStake, "total", total) diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index d60353e2ae..afc7244082 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -1939,7 +1939,7 @@ func TestAcctOnline_ExpiredOnlineCirculation(t *testing.T) { initialOnlineStake, err := oa.onlineCirculation(0, basics.Round(oa.maxBalLookback())) a.NoError(err) a.Equal(totalStake, initialOnlineStake) - initialExpired, err := oa.ExpiredOnlineCirculation(0, 1000) + initialExpired, err := oa.expiredOnlineCirculation(0, 1000) a.NoError(err) a.Equal(basics.MicroAlgos{Raw: 0}, initialExpired) @@ -2146,10 +2146,10 @@ func TestAcctOnline_ExpiredOnlineCirculation(t *testing.T) { a.Fail("unknown db seed") } a.Equal(targetVoteRnd, rnd+basics.Round(params.MaxBalLookback)) - _, err := oa.ExpiredOnlineCirculation(rnd, targetVoteRnd) + _, err := oa.expiredOnlineCirculation(rnd, targetVoteRnd) a.Error(err) a.Contains(err.Error(), fmt.Sprintf("round %d too high", rnd)) - expiredStake, err := oa.ExpiredOnlineCirculation(rnd-1, targetVoteRnd) + expiredStake, err := oa.expiredOnlineCirculation(rnd-1, targetVoteRnd) a.NoError(err) a.Equal(expectedExpiredStake, expiredStake) diff --git a/ledger/acctonlineexp.go b/ledger/acctonlineexp.go new file mode 100644 index 0000000000..83cdaea456 --- /dev/null +++ b/ledger/acctonlineexp.go @@ -0,0 +1,66 @@ +// Copyright (C) 2019-2024 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 ledger + +import ( + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-deadlock" +) + +type expiredCirculationCache struct { + cur map[expiredCirculationKey]basics.MicroAlgos + prev map[expiredCirculationKey]basics.MicroAlgos + + maxSize int + mu deadlock.RWMutex +} + +type expiredCirculationKey struct { + rnd basics.Round + voteRnd basics.Round +} + +func makeExpiredCirculationCache(maxSize int) *expiredCirculationCache { + return &expiredCirculationCache{ + cur: make(map[expiredCirculationKey]basics.MicroAlgos), + maxSize: maxSize, + } +} + +func (c *expiredCirculationCache) get(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + if stake, ok := c.cur[expiredCirculationKey{rnd: rnd, voteRnd: voteRnd}]; ok { + return stake, true + } + if stake, ok := c.prev[expiredCirculationKey{rnd: rnd, voteRnd: voteRnd}]; ok { + return stake, true + } + + return basics.MicroAlgos{}, false +} + +func (c *expiredCirculationCache) put(rnd basics.Round, voteRnd basics.Round, expiredStake basics.MicroAlgos) { + c.mu.Lock() + defer c.mu.Unlock() + if len(c.cur) >= c.maxSize { + c.prev = c.cur + c.cur = make(map[expiredCirculationKey]basics.MicroAlgos) + + } + c.cur[expiredCirculationKey{rnd: rnd, voteRnd: voteRnd}] = expiredStake +} diff --git a/ledger/acctonlineexp_test.go b/ledger/acctonlineexp_test.go new file mode 100644 index 0000000000..024a8c539c --- /dev/null +++ b/ledger/acctonlineexp_test.go @@ -0,0 +1,69 @@ +// Copyright (C) 2019-2024 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 ledger + +import ( + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestAcctOnline_ExpiredCirculationCacheBasic(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cache := makeExpiredCirculationCache(1) + + expStake1 := basics.MicroAlgos{Raw: 123} + cache.put(1, 2, expStake1) + stake, ok := cache.get(1, 2) + require.True(t, ok) + require.Equal(t, expStake1, stake) + + stake, ok = cache.get(3, 4) + require.False(t, ok) + require.Equal(t, basics.MicroAlgos{}, stake) + + expStake2 := basics.MicroAlgos{Raw: 345} + cache.put(3, 4, expStake2) + + stake, ok = cache.get(3, 4) + require.True(t, ok) + require.Equal(t, expStake2, stake) + + // ensure the old entry is still there + stake, ok = cache.get(1, 2) + require.True(t, ok) + require.Equal(t, expStake1, stake) + + // add one more, should evict the first and keep the second + expStake3 := basics.MicroAlgos{Raw: 567} + cache.put(5, 6, expStake3) + stake, ok = cache.get(5, 6) + require.True(t, ok) + require.Equal(t, expStake3, stake) + + stake, ok = cache.get(3, 4) + require.True(t, ok) + require.Equal(t, expStake2, stake) + + stake, ok = cache.get(1, 2) + require.False(t, ok) + require.Equal(t, basics.MicroAlgos{}, stake) +} From 08575d7e5ffefe312bde54ca34bda3afdd3a4adc Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:29:49 -0400 Subject: [PATCH 139/218] txHandler: fixes to ERL resources management (#6019) --- data/txHandler.go | 13 +++++++++ data/txHandler_test.go | 65 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/data/txHandler.go b/data/txHandler.go index 3d20e95acd..7851889378 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -599,6 +599,18 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net var err error var capguard *util.ErlCapacityGuard + accepted := false + defer func() { + // if we failed to put the item onto the backlog, we should release the capacity if any + if !accepted { + if capguard != nil { + if capErr := capguard.Release(); capErr != nil { + logging.Base().Warnf("Failed to release capacity to ElasticRateLimiter: %v", capErr) + } + } + } + }() + if handler.erl != nil { congestedERL := float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue)) // consume a capacity unit @@ -679,6 +691,7 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net unverifiedTxGroupHash: canonicalKey, capguard: capguard, }: + accepted = true default: // if we failed here we want to increase the corresponding metric. It might suggest that we // want to increase the queue size. diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 5eb40741ee..896fbb161d 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -28,6 +28,7 @@ import ( "runtime/pprof" "strings" "sync" + "sync/atomic" "testing" "time" @@ -794,7 +795,7 @@ func TestTxHandlerProcessIncomingCensoring(t *testing.T) { // makeTestTxHandlerOrphaned creates a tx handler without any backlog consumer. // It is caller responsibility to run a consumer thread. func makeTestTxHandlerOrphaned(backlogSize int) *TxHandler { - return makeTestTxHandlerOrphanedWithContext(context.Background(), txBacklogSize, txBacklogSize, txHandlerConfig{true, false}, 0) + return makeTestTxHandlerOrphanedWithContext(context.Background(), backlogSize, backlogSize, txHandlerConfig{true, false}, 0) } func makeTestTxHandlerOrphanedWithContext(ctx context.Context, backlogSize int, cacheSize int, txHandlerConfig txHandlerConfig, refreshInterval time.Duration) *TxHandler { @@ -2681,3 +2682,65 @@ func TestTxHandlerAppRateLimiter(t *testing.T) { msg := <-handler.backlogQueue require.Equal(t, msg.rawmsg.Data, blob, blob) } + +// TestTxHandlerCapGuard checks there is no cap guard leak in case of invalid input. +func TestTxHandlerCapGuard(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + const numUsers = 10 + addresses, secrets, genesis := makeTestGenesisAccounts(t, numUsers) + genBal := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr) + ledgerName := fmt.Sprintf("%s-mem", t.Name()) + const inMem = true + log := logging.TestingLog(t) + log.SetLevel(logging.Error) + + cfg := config.GetDefaultLocal() + cfg.EnableTxBacklogRateLimiting = true + cfg.EnableTxBacklogAppRateLimiting = false + cfg.TxIncomingFilteringFlags = 0 + cfg.TxBacklogServiceRateWindowSeconds = 1 + cfg.TxBacklogReservedCapacityPerPeer = 1 + cfg.IncomingConnectionsLimit = 1 + cfg.TxBacklogSize = 3 + + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + require.NoError(t, err) + defer ledger.Close() + + handler, err := makeTestTxHandler(ledger, cfg) + require.NoError(t, err) + defer handler.txVerificationPool.Shutdown() + defer close(handler.streamVerifierDropped) + + tx := transactions.Transaction{ + Type: protocol.PaymentTx, + Header: transactions.Header{ + Sender: addresses[0], + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: 0, + LastValid: basics.Round(proto.MaxTxnLife), + }, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: addresses[1], + Amount: basics.MicroAlgos{Raw: 1000}, + }, + } + + signedTx := tx.Sign(secrets[0]) + blob := protocol.Encode(&signedTx) + blob[0]++ // make it invalid + + var completed atomic.Bool + go func() { + for i := 0; i < 10; i++ { + outgoing := handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: mockSender{}}) + require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, outgoing) + require.Equal(t, 0, len(handler.backlogQueue)) + } + completed.Store(true) + }() + + require.Eventually(t, func() bool { return completed.Load() }, 1*time.Second, 10*time.Millisecond) +} From abf4c8d9c4decfd79e9123286057a0d581481311 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:39:47 -0400 Subject: [PATCH 140/218] agreement: log current player state for VoteBroadcast and ProposalBroadcast events (#6007) --- agreement/pseudonode.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/agreement/pseudonode.go b/agreement/pseudonode.go index 2940a826f0..32e42cd374 100644 --- a/agreement/pseudonode.go +++ b/agreement/pseudonode.go @@ -422,6 +422,9 @@ func (t pseudonodeVotesTask) execute(verifier *AsyncVoteVerifier, quit chan stru Type: logspec.VoteBroadcast, Sender: vote.R.Sender.String(), Hash: vote.R.Proposal.BlockDigest.String(), + Round: uint64(t.round), + Period: uint64(t.period), + Step: uint64(t.step), ObjectRound: uint64(vote.R.Round), ObjectPeriod: uint64(vote.R.Period), ObjectStep: uint64(vote.R.Step), @@ -549,6 +552,8 @@ func (t pseudonodeProposalsTask) execute(verifier *AsyncVoteVerifier, quit chan logEvent := logspec.AgreementEvent{ Type: logspec.ProposalBroadcast, Hash: vote.R.Proposal.BlockDigest.String(), + Round: uint64(t.round), + Period: uint64(t.period), ObjectRound: uint64(vote.R.Round), ObjectPeriod: uint64(vote.R.Period), } From 052dff4c28a56be5817841f61878f0ae2289b4d5 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:13:43 -0400 Subject: [PATCH 141/218] tests: set communication timeout in kmd.init_wallet_handle (#6021) --- test/scripts/e2e_subs/goal/goal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/scripts/e2e_subs/goal/goal.py b/test/scripts/e2e_subs/goal/goal.py index 57d8b0acb5..c68c8cdc79 100755 --- a/test/scripts/e2e_subs/goal/goal.py +++ b/test/scripts/e2e_subs/goal/goal.py @@ -121,8 +121,8 @@ def open_wallet(self, name): wallet = w assert wallet, f"No wallet named '{name}'" - self.handle = self.kmd.init_wallet_handle(wallet["id"], "") - keys = self.kmd.list_keys(self.handle) + self.handle = self.kmd.init_wallet_handle(wallet["id"], "", timeout=120) + keys = self.kmd.list_keys(self.handle, timeout=120) assert len(keys) == 1 self.account = keys[0] From 9853ab21b409f1e7afa0c1d867d70cd2e9a31ff2 Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 7 Jun 2024 14:17:30 -0400 Subject: [PATCH 142/218] Tools: Fix Cloudflare SRV update utility after API change (#6020) --- tools/network/cloudflare/createRecord.go | 8 ++------ tools/network/cloudflare/updateRecord.go | 4 +--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tools/network/cloudflare/createRecord.go b/tools/network/cloudflare/createRecord.go index efaa840575..d115e8b059 100644 --- a/tools/network/cloudflare/createRecord.go +++ b/tools/network/cloudflare/createRecord.go @@ -38,11 +38,9 @@ type createDNSRecord struct { // https://community.cloudflare.com/t/cloudflare-api-v4-srv-dns-creation-failure-in-php/25677/7 type createSRVRecord struct { Type string `json:"type"` + Name string `json:"name"` Data struct { - Name string `json:"name"` TTL uint `json:"ttl"` - Service string `json:"service"` - Proto string `json:"proto"` Weight uint `json:"weight"` Port uint `json:"port"` Priority uint `json:"priority"` @@ -90,10 +88,8 @@ func createSRVRecordRequest(zoneID string, authToken string, name string, servic requestJSON := createSRVRecord{ Type: "SRV", } - requestJSON.Data.Name = name + requestJSON.Name = service + "." + protocol + "." + name requestJSON.Data.TTL = ttl - requestJSON.Data.Service = service - requestJSON.Data.Proto = protocol requestJSON.Data.Weight = weight requestJSON.Data.Port = port requestJSON.Data.Priority = priority diff --git a/tools/network/cloudflare/updateRecord.go b/tools/network/cloudflare/updateRecord.go index 269c48f11a..7ad2b188f1 100644 --- a/tools/network/cloudflare/updateRecord.go +++ b/tools/network/cloudflare/updateRecord.go @@ -63,11 +63,9 @@ func updateSRVRecordRequest(zoneID string, authToken string, recordID string, na requestJSON := createSRVRecord{ Type: "SRV", + Name: fmt.Sprintf("%s.%s.%s", service, protocol, name), } - requestJSON.Data.Name = name requestJSON.Data.TTL = ttl - requestJSON.Data.Service = service - requestJSON.Data.Proto = protocol requestJSON.Data.Weight = weight requestJSON.Data.Port = port requestJSON.Data.Priority = priority From f06a86ec1b2a3d97200257a3500ef6f8edffa89c Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:18:09 -0400 Subject: [PATCH 143/218] rest api: /debug/settings/pprof admin endpoint for debugging (#6018) --- daemon/algod/api/algod.oas2.json | 64 ++ daemon/algod/api/algod.oas3.yml | 67 ++ daemon/algod/api/server/common/handlers.go | 2 - .../api/server/v2/generated/data/routes.go | 434 ++++++------- .../v2/generated/experimental/routes.go | 368 +++++------ .../api/server/v2/generated/model/types.go | 12 + .../nonparticipating/private/routes.go | 472 +++++++------- .../nonparticipating/public/routes.go | 596 +++++++++--------- .../generated/participating/private/routes.go | 478 +++++++------- .../generated/participating/public/routes.go | 458 +++++++------- daemon/algod/api/server/v2/handlers.go | 65 ++ .../algod/api/server/v2/test/handlers_test.go | 63 ++ 12 files changed, 1718 insertions(+), 1361 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 704b8a4638..36e4f34861 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -178,6 +178,44 @@ } } }, + "/debug/settings/pprof": { + "get": { + "description": "Retrieves the current settings for blocking and mutex profiles", + "tags": [ + "private" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "operationId": "GetDebugSettingsProf", + "responses": { + "200": { + "$ref": "#/responses/DebugSettingsProfResponse" + } + } + }, + "put": { + "description": "Enables blocking and mutex profiles, and returns the old settings", + "tags": [ + "private" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "operationId": "PutDebugSettingsProf", + "responses": { + "200": { + "$ref": "#/responses/DebugSettingsProfResponse" + } + } + } + }, "/v2/accounts/{address}": { "get": { "description": "Given a specific account public key, this call returns the accounts status, balance and spendable amounts", @@ -4053,6 +4091,26 @@ } } }, + "DebugSettingsProf": { + "description": "algod mutex and blocking profiling state.", + "type": "object", + "title": "algod mutex and blocking profiling state.", + "tags": [ + "private" + ], + "properties": { + "block-rate": { + "description": "The rate of blocking events. The profiler aims to sample an average of one blocking event per rate nanoseconds spent blocked. To turn off profiling entirely, pass rate 0.", + "example": 1000, + "type": "integer" + }, + "mutex-rate": { + "description": "The rate of mutex events. On average 1/rate events are reported. To turn off profiling entirely, pass rate 0", + "example": 1000, + "type": "integer" + } + } + }, "BuildVersion": { "tags": [ "common" @@ -5500,6 +5558,12 @@ "schema": { "$ref": "#/definitions/Version" } + }, + "DebugSettingsProfResponse": { + "description": "DebugPprof is the response to the /debug/extra/pprof endpoint", + "schema": { + "$ref": "#/definitions/DebugSettingsProf" + } } }, "securityDefinitions": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 734dc4667a..6737a95072 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -520,6 +520,16 @@ }, "description": "Teal compile Result" }, + "DebugSettingsProfResponse": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DebugSettingsProf" + } + } + }, + "description": "DebugPprof is the response to the /debug/extra/pprof endpoint" + }, "DisassembleResponse": { "content": { "application/json": { @@ -1790,6 +1800,23 @@ "title": "BuildVersion contains the current algod build version information.", "type": "object" }, + "DebugSettingsProf": { + "description": "algod mutex and blocking profiling state.", + "properties": { + "block-rate": { + "description": "The rate of blocking events. The profiler aims to sample an average of one blocking event per rate nanoseconds spent blocked. To turn off profiling entirely, pass rate 0.", + "example": 1000, + "type": "integer" + }, + "mutex-rate": { + "description": "The rate of mutex events. On average 1/rate events are reported. To turn off profiling entirely, pass rate 0", + "example": 1000, + "type": "integer" + } + }, + "title": "algod mutex and blocking profiling state.", + "type": "object" + }, "DryrunRequest": { "description": "Request data type for dryrun endpoint. Given the Transactions and simulated ledger state upload, run TEAL scripts and return debugging information.", "properties": { @@ -2748,6 +2775,46 @@ }, "openapi": "3.0.1", "paths": { + "/debug/settings/pprof": { + "get": { + "description": "Retrieves the current settings for blocking and mutex profiles", + "operationId": "GetDebugSettingsProf", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DebugSettingsProf" + } + } + }, + "description": "DebugPprof is the response to the /debug/extra/pprof endpoint" + } + }, + "tags": [ + "private" + ] + }, + "put": { + "description": "Enables blocking and mutex profiles, and returns the old settings", + "operationId": "PutDebugSettingsProf", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DebugSettingsProf" + } + } + }, + "description": "DebugPprof is the response to the /debug/extra/pprof endpoint" + } + }, + "tags": [ + "private" + ] + } + }, "/genesis": { "get": { "description": "Returns the entire genesis file in json.", diff --git a/daemon/algod/api/server/common/handlers.go b/daemon/algod/api/server/common/handlers.go index 308017e98b..c771e1443b 100644 --- a/daemon/algod/api/server/common/handlers.go +++ b/daemon/algod/api/server/common/handlers.go @@ -186,8 +186,6 @@ func VersionsHandler(ctx lib.ReqContext, context echo.Context) { }, } json.NewEncoder(w).Encode(response.Body) - - return } // CORS diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 7f5cfbeb7a..504ee50c69 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -114,221 +114,225 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "y8xbNdpOULPHJw9nH/93AAAA//8Oc6QNuAMBAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index e6472ca97a..f78cbd49a6 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -168,188 +168,192 @@ var swaggerSpec = []string{ "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", - "AwAA///pfgaenwQBAA==", + "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", + "j08ezj7+/wAAAP//hDzDXLsHAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index 1755e868ee..e2d32257ba 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -562,6 +562,15 @@ type BuildVersion struct { Minor uint64 `json:"minor"` } +// DebugSettingsProf algod mutex and blocking profiling state. +type DebugSettingsProf struct { + // BlockRate The rate of blocking events. The profiler aims to sample an average of one blocking event per rate nanoseconds spent blocked. To turn off profiling entirely, pass rate 0. + BlockRate *uint64 `json:"block-rate,omitempty"` + + // MutexRate The rate of mutex events. On average 1/rate events are reported. To turn off profiling entirely, pass rate 0 + MutexRate *uint64 `json:"mutex-rate,omitempty"` +} + // DryrunRequest Request data type for dryrun endpoint. Given the Transactions and simulated ledger state upload, run TEAL scripts and return debugging information. type DryrunRequest struct { Accounts []Account `json:"accounts"` @@ -1189,6 +1198,9 @@ type CompileResponse struct { Sourcemap *map[string]interface{} `json:"sourcemap,omitempty"` } +// DebugSettingsProfResponse algod mutex and blocking profiling state. +type DebugSettingsProfResponse = DebugSettingsProf + // DisassembleResponse defines model for DisassembleResponse. type DisassembleResponse struct { // Result disassembled Teal code diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 568ebbb8ae..bf31180ecc 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -21,6 +21,12 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + + // (GET /debug/settings/pprof) + GetDebugSettingsProf(ctx echo.Context) error + + // (PUT /debug/settings/pprof) + PutDebugSettingsProf(ctx echo.Context) error // Aborts a catchpoint catchup. // (DELETE /v2/catchup/{catchpoint}) AbortCatchup(ctx echo.Context, catchpoint string) error @@ -37,6 +43,28 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// GetDebugSettingsProf converts echo context to params. +func (w *ServerInterfaceWrapper) GetDebugSettingsProf(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetDebugSettingsProf(ctx) + return err +} + +// PutDebugSettingsProf converts echo context to params. +func (w *ServerInterfaceWrapper) PutDebugSettingsProf(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.PutDebugSettingsProf(ctx) + return err +} + // AbortCatchup converts echo context to params. func (w *ServerInterfaceWrapper) AbortCatchup(ctx echo.Context) error { var err error @@ -130,6 +158,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.GET(baseURL+"/debug/settings/pprof", wrapper.GetDebugSettingsProf, m...) + router.PUT(baseURL+"/debug/settings/pprof", wrapper.PutDebugSettingsProf, m...) router.DELETE(baseURL+"/v2/catchup/:catchpoint", wrapper.AbortCatchup, m...) router.POST(baseURL+"/v2/catchup/:catchpoint", wrapper.StartCatchup, m...) router.POST(baseURL+"/v2/shutdown", wrapper.ShutdownNode, m...) @@ -139,224 +169,230 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "NYXFJk9OHk0+/t8AAAD//5YZK+EHBgEA", + "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", + "h/8fAAD//3HzSP3DCwEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 815a346434..18ea670c4d 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -746,302 +746,306 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "IGsT4ujB5N7ow/8LAAD//5pnmKfruQEA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index d64047fe99..24f66d0cde 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -21,6 +21,12 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + + // (GET /debug/settings/pprof) + GetDebugSettingsProf(ctx echo.Context) error + + // (PUT /debug/settings/pprof) + PutDebugSettingsProf(ctx echo.Context) error // Return a list of participation keys // (GET /v2/participation) GetParticipationKeys(ctx echo.Context) error @@ -46,6 +52,28 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// GetDebugSettingsProf converts echo context to params. +func (w *ServerInterfaceWrapper) GetDebugSettingsProf(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetDebugSettingsProf(ctx) + return err +} + +// PutDebugSettingsProf converts echo context to params. +func (w *ServerInterfaceWrapper) PutDebugSettingsProf(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.PutDebugSettingsProf(ctx) + return err +} + // GetParticipationKeys converts echo context to params. func (w *ServerInterfaceWrapper) GetParticipationKeys(ctx echo.Context) error { var err error @@ -191,6 +219,8 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.GET(baseURL+"/debug/settings/pprof", wrapper.GetDebugSettingsProf, m...) + router.PUT(baseURL+"/debug/settings/pprof", wrapper.PutDebugSettingsProf, m...) router.GET(baseURL+"/v2/participation", wrapper.GetParticipationKeys, m...) router.POST(baseURL+"/v2/participation", wrapper.AddParticipationKey, m...) router.POST(baseURL+"/v2/participation/generate/:address", wrapper.GenerateParticipationKeys, m...) @@ -203,227 +233,233 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9/5PbNpI4+q+gdFflL0+csR0nt/GrrXsTO8nOxUlcHif37my/XYhsSdihAC4Azkjx", - "8//+KXQDJEiCEjUzcZKq/GSPSAKNRqPR3/vDLFebSkmQ1syefZhVXPMNWND4F89zVUubicL9VYDJtais", - "UHL2LDxjxmohV7P5TLhfK27Xs/lM8g2077jv5zMN/6qFhmL2zOoa5jOTr2HD3cB2V7m3m5G22Uplfogz", - "GuL8xezjnge8KDQYM4TyR1numJB5WRfArObS8Nw9Muxa2DWza2GY/5gJyZQEppbMrjsvs6WAsjAnYZH/", - "qkHvolX6yceX9LEFMdOqhCGcz9VmISQEqKABqtkQZhUrYIkvrbllbgYHa3jRKmaA63zNlkofAJWAiOEF", - "WW9mz97ODMgCNO5WDuIK/7vUAL9AZrlegZ29n6cWt7SgMys2iaWde+xrMHVpDcN3cY0rcQWSua9O2Pe1", - "sWwBjEv2+pvn7LPPPvvSLWTDrYXCE9noqtrZ4zXR57Nns4JbCI+HtMbLldJcFlnz/utvnuP8F36BU9/i", - "xkD6sJy5J+z8xdgCwocJEhLSwgr3oUP97ovEoWh/XsBSaZi4J/TynW5KPP9vuis5t/m6UkLaxL4wfMro", - "cZKHRZ/v42ENAJ33K4cp7QZ9+yj78v2Hx/PHjz7+29uz7H/9n59/9nHi8p834x7AQPLFvNYaZL7LVho4", - "npY1l0N8vPb0YNaqLgu25le4+XyDrN5/y9y3xDqveFk7OhG5VmflShnGPRkVsOR1aVmYmNWydGzKjeap", - "nQnDKq2uRAHF3HHf67XI1yznhobA99i1KEtHg7WBYozW0qvbc5g+xihxcN0IH7ig3y8y2nUdwARskRtk", - "eakMZFYduJ7CjcNlweILpb2rzHGXFXuzBoaTuwd02SLupKPpstwxi/taMG4YZ+FqmjOxZDtVs2vcnFJc", - "4vd+NQ5rG+aQhpvTuUfd4R1D3wAZCeQtlCqBS0ReOHdDlMmlWNUaDLteg137O0+DqZQ0wNTin5Bbt+3/", - "dfHjD0xp9j0Yw1fwiueXDGSuCihO2PmSSWUj0vC0hDh0X46tw8OVuuT/aZSjiY1ZVTy/TN/opdiIxKq+", - "51uxqTdM1psFaLel4QqximmwtZZjANGIB0hxw7fDSd/oWua4/+20HVnOUZswVcl3iLAN3/710dyDYxgv", - "S1aBLIRcMbuVo3Kcm/sweJlWtSwmiDnW7Wl0sZoKcrEUULBmlD2Q+GkOwSPkcfC0wlcEThhkFJxmlgPg", - "SNgmaMadbveEVXwFEcmcsJ88c8OnVl2CbAidLXb4qNJwJVRtmo9GYMSp90vgUlnIKg1LkaCxC48Ox2Do", - "Hc+BN14GypW0XEgoHHNGoJUFYlajMEUT7td3hrf4ghv44unYHd8+nbj7S9Xf9b07Pmm38aWMjmTi6nRP", - "/YFNS1ad7yfoh/HcRqwy+nmwkWL1xt02S1HiTfRPt38BDbVBJtBBRLibjFhJbmsNz97Jh+4vlrELy2XB", - "deF+2dBP39elFRdi5X4q6aeXaiXyC7EaQWYDa1Lhws829I8bL82O7TapV7xU6rKu4gXlHcV1sWPnL8Y2", - "mcY8ljDPGm03VjzebIMycuwXdtts5AiQo7iruHvxEnYaHLQ8X+I/2yXSE1/qX9w/VVW6r221TKHW0bG/", - "ktF84M0KZ1VVipw7JL72j91TxwSAFAnevnGKF+qzDxGIlVYVaCtoUF5VWalyXmbGcosj/buG5ezZ7N9O", - "W/vLKX1uTqPJX7qvLvAjJ7KSGJTxqjpijFdO9DF7mIVj0PgI2QSxPRSahKRNdKQkHAsu4YpLe9KqLB1+", - "0Bzgt36mFt8k7RC+eyrYKMIZvbgAQxIwvXjPsAj1DNHKEK0okK5KtWh+uH9WVS0G8flZVRE+UHoEgYIZ", - "bIWx5gEun7cnKZ7n/MUJ+zYeG0VxJcuduxxI1HB3w9LfWv4Wa2xLfg3tiPcMw+1U+sRtTUCDE/PvguJQ", - "rVir0kk9B2nFvfw3/25MZu73SR//MUgsxu04caGi5TFHOg7+Eik393uUMyQcb+45YWf9b29GNm6UPQRj", - "zlss3jXx4C/CwsYcpIQIooia/PZwrflu5oXEDIW9IZn8ZIAopOIrIRHauVOfJNvwS9oPhXh3hACm0YuI", - "lkiCbEyoXub0qD8Z2Fn+ANSa2tggiTpJtRTGol6NL7M1lCg4cxkIOiaVG1HGhA3fs4gG5mvNK6Jl/4TE", - "LiFRn6eXCNZbXrwT78QkzBG7jzYaoboxWz7IOpOQINfowfBVqfLLv3GzvoMTvghjDWkfp2Fr4AVotuZm", - "nTg4PdpuR5tC3+5FpFm2iKY6aZb4Uq3MHSyxVMewrqp6zsvSTT1kWb3V4sCTDnJZMvcyg41Ag7lXHMnC", - "TvoX+5rnaycWsJyX5bw1FakqK+EKSqe0CylBz5ldc9sefhw56DV4jgw4ZmeBRavxZiY0senGFqGBbTje", - "QBunzVRl95uGgxq+gZ4UhDeiqtGKECka5y/C6uAKJPKkZmgEv1kjWmviwU/c3P4RziwVLY4sgDa47xr8", - "NfyiA7R7u71PZTuF0gXZrK37TWiWK01D0A3vJ3f/Aa7bj4k671caMj+E5legDS/d6nqLetCQ712dzgMn", - "s+CWRyfTU2FaASPOgd+heAc6YaX5Ef/DS+YeOynGUVJLPQKFERW5Uwu6mB2qaCb3AtpbFduQKZNVPL88", - "Csrn7eRpNjPp5H1N1lO/hX4RzQ692YrC3NU24WBje9U9IWS7CuxoIIvsZTrRXFMQ8EZVjNhHDwTiFDga", - "IURt7/xa+0ptUzB9pbaDK01t4U52wo0zmdl/pbYvPGRKH8Y8jj0F6W6Bkm/A4O0mY8bpZmn9cmcLpW8m", - "TfQuGMlabyPjbtRImJr3kISv1lXmz2bCY0Ev9AZqAzz2CwH94VMY62DhwvJfAQvGjXoXWOgOdNdYUJtK", - "lHAHpL9OCnELbuCzJ+zib2efP37y9yeff+FIstJqpfmGLXYWDLvvzXLM2F0JD5LaEUoX6dG/eBp8VN1x", - "U+MYVescNrwaDkW+L9J+6TXm3htirYtmXHUD4CSOCO5qI7Qzcus60F4I43SnzeJONmMMYUU7S8E8JAUc", - "JKZjl9dOs4uXqHe6vguzAGitdPLqqrSyKldl5uQjoRKK/Sv/BvNvBMtG1f+doGXX3DA3N3r9almM6O92", - "K6fzfRr6zVa2uNnL+Wm9idX5eafsSxf5rfRegc7sVrICFvWqY1ZYarVhnBX4Id7R34IluUVs4MLyTfXj", - "cnk3VkKFAyXsH2IDxs3E6A0nNRjIlaQguAOmDj/qFPT0ERO8M3YcAI+Ri53M0cV0F8d23Aq0ERL93WYn", - "88gk5GAsoVh1yPL2pp8xdNBU90wCHIeOl/gYbdwvoLT8G6XftGLft1rV1Z0Lef05py6H+8V4K3rhvg3m", - "UyFXZTfwcuVgP0mt8TdZ0PNG+aY1IPRIkS/Fam0jPeuVVmp59zCmZkkBig/IyFK6b4amlh9U4ZiJrc0d", - "iGDtYC2Hc3Qb8zW+ULVlnElVAG5+bdLC2UioHsYIYWiTjeU91OuFYQtw1JXz2q22rhgG7gzui/bDjOd0", - "QjNEjRkJW2jiTegtmo7CwEoNvNixBYBkauFjA3zUAi6SY9SRDeKNFw0T/KIDV6VVDsZAkXkT7kHQwnt0", - "ddg9eELAEeBmFmYUW3J9a2Avrw7CeQm7DGPkDLv/3c/mwW8Ar1WWlwcQi++k0Nu3Qw2hnjb9PoLrTx6T", - "HVm4iGqZVSjNlmBhDIVH4WR0//oQDXbx9mi5Ao2hGL8qxYdJbkdADai/Mr3fFtq6Gon89uqtk/Dchkku", - "VRCsUoOV3NjsEFt2L3V0cLeCiBOmODEOPCJ4veTGUviQkAXaAuk6wXlICHNTjAM8qoa4kX8OGshw7Nzd", - "g9LUplFHTF1VSlsoUmtAT+boXD/AtplLLaOxG53HKlYbODTyGJai8T2yaCWEIG4bv6X3hA4Xh75od8/v", - "kqjsANEiYh8gF+GtCLtx9OsIIMK0iCbCEaZHOU3I7XxmrKoqxy1sVsvmuzE0XdDbZ/an9t0hcZFzgO7t", - "QoFBx4N/30N+TZiluOc1N8zDEVzTaAahOKchzO4wZkbIHLJ9lI8qnnsrPgIHD2ldrTQvICug5LuEU50e", - "M3q8bwDc8VbdVRYyCmBNb3pLySFecM/QCsczKeGR4ROWuyPoVIGWQPzXB0YuAMdOMSdPR/eaoXCu5BaF", - "8XDZtNWJEfE2vFLW7binBwTZc/QpAI/goRn65qjAj7NW9+xP8T9g/ASNHHH8JDswY0toxz9qASM2VJ8b", - "FJ2XHnvvceAk2xxlYwf4yNiRHTHovuLailxUqOt8B7s7V/36EyQdzqwAy0UJBYsekBpYxd8zCr3sj3kz", - "VXCS7W0I/sD4llhOCG/pAn8JO9S5X1FMf2TquAtdNjGqu5+4ZAhoiBR2Inj8Cmx5bsudE9TsGnbsGjQw", - "Uy/I9T/0Q1hVZfEASb/Gnhm9VzPpU9zrZr3AoaLlpWK0SCfYD9+bnmLQQYfXBSqlygkWsgEykhBMirlg", - "lXK7LnzaUEgcCZTUAdIzbXRpN9f/PdNBM66A/Y+qWc4lqly1hUamURoFBRQg3QxOBGvm9EF9LYaghA2Q", - "JolPHj7sL/zhQ7/nwrAlXIdcO/diHx0PH6Id55UytnO47sAe6o7beeL6QIePu/i8FtLnKYcjhfzIU3by", - "VW/wxkvkzpQxnnDd8m/NAHoncztl7TGNTIuSwnEn+XK6cTWDdeO+X4hNXXJ7F14ruOJlpq5Aa1HAQU7u", - "JxZKfn3Fyx+bzzCPEHJHozlkOWa/TRwL3rhvKGHOjSOkcAeYguWnAgTn9NUFfXRAxWwjPMVmA4XgFsod", - "qzTkQHliTnI0zVJPGEWQ52suV6gwaFWvfFAojYMMvzZkmtG1HAyRFKrsVmZo5E5dAD68K6QKOnEKuFPp", - "+hZyUmCueTOfzw6dcjNHe9D3GCSdZPPZqMbrkHrVaryEnG6+44TLoCPvRfhpJ57oSkHUOdlniK94W9xh", - "cpv765js26FTUA4njiJl24djwbJO3S53dyD00EBMQ6XB4BUVm6kMPVXLOLc5hNjtjIXN0JJPn/595Pi9", - "HtUXlSyFhGyjJOyS5TyEhO/xYfI44TU58jEKLGPf9nWQDvw9sLrzTKHG2+IXd7t/QvseK/ON0nflEqUB", - "J4v3EzyQB93tfsqb+kl5WSZciz7zsc8AzLwJchWacWNULlBmOy/M3EfTkjfSp0l20f+qyee4g7PXH7fn", - "Q4uT6tFGDGXFOMtLgRZkJY3VdW7fSY42qmipieCnoIyPWy2fh1fSZtKEFdMP9U5yDHxrLFfJgI0lJMw0", - "3wAE46WpVyswtqfrLAHeSf+WkKyWwuJcG3dcMjovFWiMQDqhNzd8x5aOJqxiv4BWbFHbrvSPib3GirL0", - "Dj03DVPLd5JbVgI3ln0v5JstDhec/uHISrDXSl82WEjf7iuQYITJ0kFa39JTjIf3y1/72HgME6fHIViz", - "rTQwc8vsFBf5/+7/57O3Z9n/8uyXR9mX/9fp+w9PPz54OPjxyce//vX/7/702ce/PvjPf0/tVIA9lXbq", - "IT9/4TXj8xeo/kQh7n3YP5n9fyNkliSyOJqjR1vsPpZY8AT0oGscs2t4J+1WOkK64qUoHG+5CTn0b5jB", - "WaTT0aOazkb0jGFhrUcqFbfgMizBZHqs8cZS1DCuMZ3gjU5Jn7ON52VZS9rKIH1T/mKIL1PLeZPET/W9", - "njHM8F7zEBzp/3zy+RezeZuZ3TyfzWf+6fsEJYtim8q/L2Cb0hXj5IJ7hlV8Z8CmuQfCngylo9iOeNgN", - "bBagzVpUn55TGCsWaQ4XUn28zWkrzyUFxrvzgy7OnfecqOWnh9tqgAIqu07V/ekIavhWu5sAvbCTSqsr", - "kHMmTuCkb/MpnL7og/pK4MuQ/qKVmqINNeeACC1QRYT1eCGTDCsp+umlBfjL39y5OuQHTsHVn7PxZ4a/", - "rWL3vv36DTv1DNPco1IQNHSUvJ9QpX3SYScgyXGzOBfrnXwnX8ASrQ9KPnsnC2756YIbkZvT2oD+ipdc", - "5nCyUuxZyGN8wS1/JweS1mhBwijZmFX1ohQ5u4wVkpY8qcjUcIR3797ycqXevXs/iM0Yqg9+qiR/oQky", - "Jwir2ma+RE6m4ZrrlO/LNCVScGSqgbVvVhKyVU0G0lCCx4+f5nm8qky/VMJw+VVVuuVHZGh8IQC3ZcxY", - "1eRxOQHFp8K6/f1B+YtB8+tgV6kNGPaPDa/eCmnfs+xd/ejRZ5gR19YO+Ie/8h1N7iqYbF0ZLeXQN6rg", - "wkmthK3VPKv4KuVie/furQVe4e6jvLxBG0dZMvysk60XAvNxqHYBTWrw6AYQHEcn1eLiLuirUA4xvQR8", - "hFvYTVy+1X5Feec33q4Dueu8tuvMne3kqowj8bAzTZW0lROyQjSGESvUVn1BuQWwfA35pa/0BZvK7uad", - "z0PAjxc0A+sQhmrAUWYeViFCB8UCWF0V3IviXO765WAMWBvCil/DJezeqLaI0TH1X7rlSMzYQUVKjaRL", - "R6zxsfVj9DffR5WFBE1f1QOTHgNZPGvoInwzfpBJ5L2DQ5wiik65jDFEcJ1ABBH/CApusFA33q1IP7U8", - "IXOQVlxBBqVYiUWqfO1/D/1hAVZHlb5in49CbgY0TCyZU+UXdLF69V5zuQJ3PbsrVRleUjXSZNAG6kNr", - "4NougNu9dn4ZF3II0KFKeY0Zy2jhm7slwNbtt7BosZNw7bQKNBTROz56+WQ8/owAh+KG8ITPW03hZFTX", - "9ahLVOoLt3KD3Uat9aF5MZ0hXPR8A1jqU127fXFQKF+lkoqhRPdLbfgKRnSX2Hs3sY5Ex+OHgxySSJIy", - "iFr2RY2BJJAEmV7O3JqTZxjcE3eIUc3sBWSGmchB7H1GWHzaI2xRogDbRK7S3nPd8aJSNd0x0NKsBbRs", - "RcEARhcj8XFccxOOI9YZDVx2knT2K5ZL2VfS7TyKJYyKiTYF28Jt2OegA73fF3YL1dxCCbdY6Z9Qjs3p", - "Xpi+kNoOJVE0LaCEFS2cXg6E0hYaajfIwfHjcom8JUuFJUYG6kgA8HOA01weMka+ETZ5hBQZR2Bj4AMO", - "zH5Q8dmUq2OAlL5QEg9j4xUR/Q3pxD4K1HfCqKrc5SpG/I154AC+hEMrWfQiqnEYJuScOTZ3xUvH5rwu", - "3g4yqCyGCkWvjpgPvXkwpmjscU3RlX/UmkhIuMlqYmk2AJ0WtfdAvFDbjDJ7k7rIYrtw9J7MXcA849TB", - "pBpu9wxbqC2Gc+HVQrHyB2AZhyOAEdletsIgveJ3Y3IWAbNv2v1ybooKDZKMN7Q25DIm6E2ZekS2HCOX", - "+1FZthsB0DNDtT0OvFnioPmgK54ML/P2Vpu35UZDWljq+I8doeQujeBvaB/rFlL7W1swb7woVzhRn6SC", - "3NCydJvKfvRxRdX6jins1yeHDhB7sPqqLwcm0dqN9eriNcJaipU45jt0Sg7RZqAEVIKzjmiaXaYiBZwu", - "D3iPX4TPImMd7h6XuwdRAKGGlTAWWqdRiAv6LczxHMsOK7UcX52t9NKt77VSzeVPbnP8sLPMT74CjMBf", - "Cm1shh635BLcS98YNCJ9415NS6DdEEUq0i+KNMfFaS9hlxWirNP06uf97oWb9ofmojH1Am8xISlAa4FN", - "JZKBy3umptj2vQt+SQt+ye9svdNOg3vVTawduXTn+IOcix4D28cOEgSYIo7hro2idA+DjBLOh9wxkkaj", - "mJaTfd6GwWEqwtgHo9RC2vvYzU8jJdcSlc9LZwiq1QqKUBYs+MNkVHytVHIVdT+qqn215k4YlXzDim17", - "ir35MHwYC8KPxP1MyAK2aehjrQAhbzPrsFAdTrICSeVK0mahJGriEH98I7LVfWJfaD8BIBkE/abnzG6j", - "k2mXmu3EDSiBF14nMRDWt/9YDjfEo24+Fj7dqRi6/wjhgEhTwkYNQYZlCEYYMK8qUWx7jicaddQIxo+y", - "Lo9IW8ha/GAHMNANgk4SXKcEtQ+19gb2U9R5T51WRrHXPrDY0TfPfQJ+UWv0YHQim4f1zhtdbeLav/v5", - "wirNV+C9UBmBdKshcDnHoCGqJm6YFRROUojlEmLvi7mJ56AD3MDGXkwg3QSRpV00tZD2i6cpMjpAPS2M", - "h1GWppgELYz55N8MvVxBpo9MSc2VEG3NDVxVyXT972CX/czL2ikZQps2PNe7nbqX7xG7frX5DnY48sGo", - "VwfYgV1By9NrQBpMWfqbRyYq/HzPdErjo3rZ2cIjduosvUt3tDW+mcE48be3TKfYf3cptzkYbZCEg2XK", - "blykYxPc6YEu4vukfGgTRHFYBonk/XgqYULrx+FV1NSiOES7b4CXgXhxObOP89ntIgFSt5kf8QCuXzUX", - "aBLPGGlKnuFOYM+RKOdVpdUVLzMfLzF2+Wt15S9/fD2EV3xiTSZN2W++Pnv5yoP/cT7LS+A6aywBo6vC", - "96o/zKqo/cH+q4SqZHtDJ1mKos1vKhnHMRbXWBG7Z2waNBNp42eio+hjLpbpgPeDvM+H+tAS94T8QNVE", - "/LQ+Twr46Qb58CsuyuBsDNCOBKfj4qZ1pElyhXiAWwcLRTFf2Z2ym8HpTp+OlroO8CSc60csTZnWOKQv", - "XImsyAf/8DuXnr5RusP8fWZiMnjo1xOrnJBNeByJ1Q59H/vC1Akjwesfq3+40/jwYXzUHj6cs3+U/kEE", - "IP6+8L+jfvHwYdJ7mDRjOSaBVirJN/CgybIY3YhPq4BLuJ52QZ9dbRrJUo2TYUOhFAUU0H3tsXethcdn", - "4X8poAT308kUJT3edEJ3DMyUE3QxlonYBJluqNWkYUr2Y6oxCdaRFjJ738qAnLHDIyTrDTowM1OKPB3a", - "IRfGsVdJwZTuZYYvj1hr3Yi1GInNlbWIxnKvTamZ2gMymiOJTJMs29ribqH88a6l+FcNTBROq1kK0Hiv", - "9a66oBzgqAOBNG0X8wOTn6od/jZ2kD3+pmAL2mcE2eu/e9H4lMJCU81yjowAj2ccMO490duePjw1Uzbb", - "uhuCOU2PmdJyPDA676wbmSPZQlyYbKnVL5B2hKD/KFEIIzg+BZp5fwGZitzrs5TGqdx2Qm9nP7Td03Xj", - "sY2/tS4cFt1067rJZZo+1cdt5E2UXpMu1+yRPKaExREG3dSAEdaCxysKhsX2ISH6iEs6T1QFopNhlj6V", - "cS7nKY3fnkoP8yD/teTXC57qreJ0IQdTtL2dOCmrWPg4bIBpahzQ7CyK4G7eFVRJrgLd+iCGVWlvqNfQ", - "tJM1mlaBQYqKVZc5hSmURiWGqeU1l9R9231H/Mp/bYBc8O6ra6WxDqRJh3QVkItN0hz77t3bIh+G7xRi", - "JaixdG0g6lzsB6Km/URFvvtzU7nDo+Z8yR7No/bpfjcKcSWMWJSAbzymNxbc4HXZuMObT9zyQNq1wdef", - "THh9XctCQ2HXhhBrFGt0TxTymsDEBdhrAMke4XuPv2T3MSTTiCt44LDohaDZs8dfYkAN/fEodcv6xuD7", - "WHaBPDsEa6fpGGNSaQzHJP2o6ejrpQb4BcZvhz2niT6dcpbwTX+hHD5LGy75CtL5GZsDMNG3uJvozu/h", - "RZI3AIzVaseETc8Pljv+NJLz7dgfgcFytdkIu/GBe0ZtHD21bYlp0jAc9cj3fZYCXOEhxr9WIfyvZ+v6", - "xGoM34zkbGGU8g/oo43ROmecin+Woo1MD30u2XmoLYyNp5p+U4QbN5dbOsqSGKi+ZJUW0qL9o7bL7C9O", - "LdY8d+zvZAzcbPHF00QDp26PE3kc4J8c7xoM6Ks06vUI2QeZxX/L7ksls43jKMWDtsZCdCpHA3XTIZlj", - "caH7h54q+bpRslFyqzvkxiNOfSvCk3sGvCUpNus5ih6PXtknp8xap8mD126Hfnr90ksZG6VTDQPa4+4l", - "Dg1WC7jCjLn0Jrkxb7kXupy0C7eB/reNfwoiZySWhbOcVAQij+a+ZHknxf/8fVv5HB2rlInYswEqnbB2", - "ervdJ442PM7q1vffUsAYPhvB3GS04ShDrIxE31N4ffPNbxEv1AeJ9rxjcHz8D6adDo5y/MOHCPTDh3Mv", - "Bv/jSfcxsfeHD9MFiJMmN/dri4XbaMT4bWoPv1IJA1jo9tcEFPn6CAkD5Ngl5R44JrjwQ81Zt7Pap5ci", - "7ia/Kx1tmj4F7969xScBD/hHHxG/MbPEDWyzFMYPe7ezZJJkiuZ5FOfO2VdqO5VwendQIJ7fAYpGUDLR", - "PIcrGXTOTLrrD8aLRDTqRl1AqZySGTcFiu35fxw8u8XP92C7FmXxc1vbrXeRaC7zdTJKeOE+/DvJ6J0r", - "mFhlss/ImksJZXI40m3/HnTghJb+TzV1no2QE9/td26l5fYW1wLeBTMAFSZ06BW2dBPEWO2WzWrKMpQr", - "VTCcp21q0TLHYQvkqC/jv2owNnU08AElIKKzyzFfagvIQBZo/Tph32IBGwdLp2I5Wp1CLdhuXcS6KhUv", - "5lij9s3XZy8ZzUrfUAdtaku4QqNLdxVJK/kRfda90XmkAMox/dr3VWRwqzY2a7oIpkrMuTfaPoeiFzqB", - "5pgYOyfsBVnCmv7lNAnDSsd6A0XUtJB0MaQJ9x9reb5GE1PnIhsn+en9NANVtgb4KImsaWKD587B7Vtq", - "UkfNOVN2DfpaGMDEariCblW7psSjN3GGKnfd5elaSqKUkyNkiqZlzbFoD8CRQBJ8w0nIeog/0sBA7WiP", - "bS96gV+lQ+p7vUp7zttQI61puv69txHnXCopcqxonxKIsALXNG/ThOL/aTeRmfkTmjhcyQ6pTUqnx+Jo", - "z9TACD3ihp7b6KnbVKIO+tPC1nfOWoE1nrNBMQ+Nfr1fQ0gDvimRI6KYTyqdiE1JxrM3fvAjyQiL64wY", - "qr5xz37wZkysbXApJBosPNq8mE2eh9IIdDBKJixbKTB+Pd2kDPPWfXOCxfYK2L4/ealWIr8QKxyDoqHc", - "sin0bzjUWQgE9IF37t3n7l1fAr35uRPVQ5OeVZWfdLwNdLr3/VaOIjgVfhLiASLkNuPHo+0ht70RvHif", - "OkKDKww+ggrv4QFhNC2Ru6N87VQEoih8g1FiXLIOqpAJMF4KGTxh6QsiT14JuDF4Xke+M7nmlkTASTzt", - "DfByJI4dE03JlXrbofoF4B1KcI1hjvFtbLs5jzCO5oVWcONyx8KhcNQdCRPPedlEwCZ6M6NU5YWoAnNE", - "et2aU4zDMe7QD757ARzMwmo+x6YKx95EY6XmFnWxApvxokhVKPoKnzJ8GnJ9YAt53fQSapK8uqWmh9Tm", - "J8qVNPVmz1zhhVtOF7U/T1BD3II97DAWTFns8N9UI53xnfGxr0cnV4ZA1+K4+urDZNGU1OtoOjNilU3H", - "BN4pt0dHO/XNCL39/k4pPWRd/i6SKntcLt6jFH/72l0ccf3VQZgxXS1NeVQM6VX4PNStaQr7dbkSXmWD", - "dlHovMbNS2xZD/jwYhLwK16OJDTHJm+6X8kMPJbWnI9m4XPrqyxZzvayoNHKNRTy2TOiDz1BY2GeFOV5", - "d8Znv9a9CB13wXzXcbhQqE/LLEYdLTfzhbQbfKwz5LursUz30G4Bn/fb31+CL4pZabgSqg5BNCGUNaiE", - "9GunmXxTayC5/mSA+G9tfB41lb/xbUhpmV4n/+5ncqYxkFbvfgeG88GmDxrrD6VdMk+1r7Cmg92kjnad", - "W3FKK5JU1wsvG3Za+3dpadBFZEBWL6aIAwN8fJzPzoujLsxU55QZjZI6di/Fam2x8PrfgBegXx0oLN8W", - "k8cjVikj2kaSpRvMV/Jc43AnU2PGHQGLuDD+cKwQS3gFucXuoW2MlAY4pky+myzY7v8sMD+uTjeh9b6u", - "/L5i8sOWoQfu+EH9m6iGE7VbPJleOv2siYSlRJ5rbtqqG73U18kJeMsl5Fjcdm+9of9eg4xq2cyDXQZh", - "WUblh0STjoLlmY+3OrYA7SsHtBeeqE3KrcEZS0e+hN09wzrUkOz/2ORi3aT+K2IAuUMWSgGPGZJ98I8w", - "DWUgFkJkp6+o2/Y4GC3dG1XPuuFcgSTdxdFW1NozZbp39aS53KdHVe/DzIqxkkTD1rfj+scL7DRsfJwT", - "b+rHxlo6Ox/2P7n29WexOlTjOwmVaMGE30IpOJqlFJcQN7dHT9U110V4405q+9DdJNJAL5uZRRuHP/RV", - "JyrqY0pLXionRmRjeUHd0PcmbuyeoQC/tg4LwrUEraFoXCKlMpBZFeL298GxDxUUxXgjJJjRLjYE3GgF", - "49dtiWbs5sWxYjH3wYvxApmGDXfQ6aiQ8vic+5D9nJ6HXOrQzemghamh18NtRUMGhjADJMZUv2T+tjyc", - "o30TY5OQEnQWPE/9qsqyW1gLyycWdU4XdHwwGoPc5BIoe1hJ0k6TD1fZ0xGiXOdL2J2SEhT6sYYdjIEm", - "yYlAj+pG9jb5Ts1vJgX36k7A+23LgVVKldmIs+N8WAq6T/GXIr8ELOXWRCqPtNpm99HG3nizr9e7UPq4", - "qkBC8eCEsTNJuSHBsd3tEtebXN6z++bf4qxFTdXZvVHt5J1MB9lj3XR9S24WhtnPwww4VnfLqWiQA4WG", - "tyNlqDW/TjSeP5mqlQ9dzf1m4C1RERQpmeSCPFbP8aCnDEeYyR6VXEBHJmfe08VMqVIhmTfJtndDpTEV", - "T4YAWZBTkr4bKPzgSQQk21snTiFVMPO1y9SSaWidyDct4jbsxJ3S6PszN7N0+d1Saej01HZfU8HGJn8h", - "NL/neiGs5np3k1Jrg07gA+vJKJYPhmM1kVjtQtporCEOy1JdZ8issqZdQUq1de+Z7mUceme137lTvYAo", - "rosbL6jt2JoXLFdaQx5/kU7bI6g2SkNWKgzzSnmgl9bJ3RvM1ZGsVCumqlwVQG0/0hQ0NlctJUexCaKo", - "miQKiHYw6ZO+ieh44pR31YaeivPQojPyZY4EnoLxxXg8hujlIbx7Wrgf1XDjfIkWIYGxLt3ca5I+40b2", - "cGQfe1GWwWAw1sqe/WRqDEfCxBs3xVO2UcZ6zY5GMs1QbYjX/VxJq1VZdo1AJBKvvGX7e749y3P7UqnL", - "Bc8vH6AeKZVtVlrMQ1pqPxivnUn3KjJN7Lnfr3BK72FomieSoxvre85xdD/sCMz3hznWYRv3WaIPfm9d", - "XeaVVhvOJONWbUSepuE/VnTbaExaiiUkSz1RSzpKzsfXkFHHl0MTzIAsaYhmkDzZU+uMeZ7mnbrIPNx/", - "UeLtj8uW4C+JkYtpyCe91JLlo7JVDwCElDJGba2pj10s+TRcRa0owxxd0n1AJ3JxjPy5HWxuhDsHysKt", - "gBpEGzYA3idlf04luShycaG24fmDtmbXjYD/uJ/KO8xjLKTqoiUtTUFVob7HCEdIVwbeG3/0BrOFF1Oj", - "kJqeoxNv1AiA8bikDgyTopOOBWPJRQlFlmpZd97YhOaRZuszWvqdpIXxnDzndegY58auNfh6EyRS666/", - "qeKOlFTz+tByKwvYgsFiENQ+nxvyMwR/B5TUKa6nfKsqK+EKOuFavghGjaKduILwrWk+ZgVAhd6/vk0q", - "FYcU3+U9Q4VfexZFskzBbtJyQYilnWIHzBJJI8pWZnRMzNSj5CC6EkXNO/gzx4ocXbObO8oJVA1k8izo", - "bVOn+YlGeB0GOAvfp0SZgIn30/jQ0Swojbp9DOhgXGJtxk69TIclxhVeGocGzlY0jk8i8ZZvmIpfy3ED", - "4JDkW/Vm4j4JJSPEfr2FHKWabtzd7XHCcDBmetWbRkVw3ezwzQ3JvwkN7yXh0fFSqoYBZLB7LTWBLrzA", - "ji9g72DpxF4nNWNXOM//Pf+bs0UdBnJ6NTWpizW4FxA8dlhQunFWeIFWNBdaiC+c+3qCfaVcRJHVG75j", - "SuM/Tl/7V81LsdzhCSXww2fMrLkjIe8iJN+1j1d0E+8XTOYBsGAXUGEqWreYOmY03M6NEgHtrsDQTUSx", - "Db+EeBvQLU+cJ7eO5Zh6sRHG4GXX284hFvziQ02IDS9iHRkr03X7Nodape7r/7vN2oqnCgWlqpLnoSWh", - "74nSMYhT29FAXHYNm/1pfUP1OJBA08q0JVod0nmLGxj3jozcSMXKj/V76IA9aPE4aHVxq2Uc0w2+zYze", - "kxA5aSl3vQtT40MGQMeN4Q6BH/fJ+zT4TxaNHFvGFPB/L3gf6YwZw0tNMD8Bljsp/wlYya66UNtMw9Ic", - "CoUgw6pThHVbLCAYJ4XMNXBDsSHnP3qVra2JKKRTISl6sfG+NaMUsBSyZZZCVrVNaABYGlHuIoTF5mlE", - "64izZ0xKcGLYFS9/vAKtRTG2ce50UA+5uCZ9MMn7bxPKf3OnDgcQptV+MJMQ2ky16DV3gVPXGwosNJbL", - "gusifl1IloN29z675jtzc9+Hg1bXTr444P3gkTTTzW+P/CBI2gRIufPuy1t6JhoA+R26KCa4FjCCNeFW", - "IKOIVSOehCEM6bIKfJuVaoX5ZSME6ItPou+HlBUl0WBL8tBx8xjxC+yfButu+4NvFc46ZYr95+xHRB0q", - "PD9JYfeeNLKm9RP+KCKTDkKgf7lqw8Jpc4b0n8rRfINJDJ08zSDchSSGsNcUHkLzwYgno2vBHdlFdJD7", - "BN/YXDu9n1HXB5/KBCUdNkPd1uwJ/AbTBjnz3AfuDI0+A6WYkDL3ebRH2oTIkhzugRHwqPm0P1vdaZtg", - "CjfOMU2g9mfOZpWqsnxKNCCV5i+8QdtD2oVxhD4ic/XIupvACdM0q+gUNul0rTi2D9Zo14xDfpkq36dk", - "jxk0Rjho11iulsjLqDUz2mEwx6MxXsz72Uddg03DJBhnGvJao0Hzmu8O9xUaKQl78bezzx8/+fuTz79g", - "7gVWiBWYtqxwry9PGzEmZN/O8mljxAbLs+lNCHnphLjgKQvpNs2m+LNG3Na0NQMHXYmOsYQmLoDEcUz0", - "g7nRXuE4bdD372u7Uou88x1LoeDX3zOtyjJd1r0R3RKm/tRuRcZ+J/FXoI0w1jHCrq9O2DZW1qzRHIfF", - "Pa+ozoiSua++3lCBsCPBOKmFjIVaIj/DrF/v32CwrUrPq8gnsW9dXi8iixgGZ2D8xgJYpSovSoslS0GE", - "uSU6yrn0hkYM74yiJxtmS3GUKUL0Mclp0os74u7n9t1ujTbN6d0mJsSLcChvQJpjlvTxjPabcJLWlP67", - "4R+JFP074xrNcn8NXpHUD27WdXsSaMN07QR5IAAjeZidDLq4KX9baVSTVR7t98HV2Rc/vm9doAcTBhCS", - "8MEB8OLEyva9Jsbdg/Mbl+z8vkFKtJT3Y5TQWf6hXM3AepuLJNoib6SwFgyxJTUUC6NEXPO8yW8d0UoG", - "abDYgd9ppmWZSJ8luwmeqZhwnEqgr3j56bnGN0Ibe4b4gOL1eNJMnEMZI5lQaW5Wwe0lnzR3lC95d1PL", - "V5iy+9/g9ih5z/mhvLt4cJuh1QtbUq/CrUBZwOwax6RwoMdfsIWvpl9pyIXpu6Gvg3DSpAyCFksfeglb", - "eyBH8dA6f1b2FmS8DDEj7IfInaTQbNdC2B7R35ipjJzcJJWnqG9AFgn8pXhU3H3zwHVxy8rrNysIEpX2", - "OrIgyLCv6NTlUdELd+nUBobrnHxbd3CbuKjbtU2tZjO5gPu7d2/tYkoRmnSxdfc5VsG5k6rrR9Vc/xXq", - "3xCO/Bh+3hTF/DxWEZWqfo4U3+3tRy3KgwEinVLKH+ezFUgwwmCx4L/75hCf9i4NEFBO/vCoEqy3KSRC", - "iEmstTN5NFVUJHlCfWT/WaIaMua75bUWdoeNQYMBTfw9Wann26bqg68a0viu/N1n1SU0zZnbGhG1Cbfr", - "t4qXeB+RS026W0iVJ+zrLd9UpTcHs7/eW/wHfPaXp8Wjzx7/x+Ivjz5/lMPTz7989Ih/+ZQ//vKzx/Dk", - "L58/fQSPl198uXhSPHn6ZPH0ydMvPv8y/+zp48XTL778j3uODzmQCdBQu/vZ7P/NzsqVys5enWdvHLAt", - "TnglvgO3N6grLxU2rnNIzfEkwoaLcvYs/PT/hBN2kqtNO3z4deYbsMzW1lbm2enp9fX1SfzJ6QqTwjOr", - "6nx9GubBdmIdeeXVeRNNTnEvuKOt9Rg31ZPCGT57/fXFG3b26vykJZjZs9mjk0cnj33vWskrMXs2+wx/", - "wtOzxn0/9cQ2e/bh43x2ugZeYg0V98cGrBZ5eKSBFzv/f3PNVyvQJ5gwQD9dPTkNYsXpB58c/3Hfs9M4", - "pOL0Q6eGQHHgyxAycOiV0w+hyeX+ATsNDn2wVvTBRED3vXa6wMYWU1+FeHXjS0F9xJx+QIl69PdTbxZJ", - "P0TNho7MaajFMfImZV2nH3ZQ+MFu3UL2D+feicbLuc3XdXX6Af+D1B+tiIo4ntqtPEXP6+mHDiL84wEi", - "ur+3n8dvXG1UAQE4tVxSZ9B9j08/0L/RRLCtQAsnVmLhFP8rFbg6xQZRu+HPO5knfxyuo1Pcx53bpBf7", - "NVWU56wUJoQjdGsCmbh59HmB/N32Cw25l0IoIjKJJ48eBc7o9Y6IZE89E5i1reSnlS3olzca3phD1rhv", - "ZR/ns6dHArrXttQpCpkA5itesJDDinM//nRzn0sKi3R3Bd1pCMHTTwdBZ/vYd7BjPyjLvkHl6+N89vmn", - "3Ilz6URBXjJ8M2rQOTwiP8lLqa5leNMJQ/Vmw/Vu8vGx3HHBt7NKiyvuRdHmNbmavccaDpTX3D1qZ0Ux", - "IHoSCsHYrxTermMY25hV5d0uLdJamVhIt4ShUj1A1RvqU9urFEb1bILzXaoCZrG0anUNH2/JE3oBG1zb", - "84SNCI2dGCm9DC11I1CTZa/67mwaeajPHCLhtutzG2D8J0/5k6c0POXzR599uukvQF+JHNgb2FRKcy3K", - "HftJNpHrN+ZxZ0WRrBXYPfoHedx8ts1yVcAKZOYZWLZQxS50tu9McAmk/g4EmdOgLnY0hhHuGRTRlLTS", - "xlPOnr1N+Tl9G9aqXpQiZ2QqQ13RKUKRKtcUb+syv3m0rQP2kygQzApR1k0isb1WPlFveKGw+3F6vfkX", - "dWjHgyjsjl0LWahrbE+N4P6rBuTzHt4wzSwBYBS0N+yF0XoAHIADsMbmQ9fBFOzsmfwlv9ncJT926ve3", - "vLIOXqZNcaT/uvjxhyidh1KQycOPySREuhj5qxVGtF5zDPHSFooT9pxMN+WOSYVOgtp02vWc/HkP/cn7", - "b8/7v22qZVKjHosdOIYsKboLTiYJvEne/qHzp7dbzCi+MlXj0v3OOFthk7XhBbXYsfMXA+2VPutfCV/t", - "8NXerZDg930Qj2L8I+xln0jjFrJStokypUX9KWT+KWTeSnGdfHim6K5JyxK1PuQDfWweuhim+nFzOwRl", - "iv3pNz2+d7LxQ9tWypZF9XShYNEDSu/uo/lPFvEni7gdi/gWEocRT61nGgmiO87WNZVhYBWPohMzFaSO", - "8Hpdch1l1B0yYZ/hiGlV8FfhGp/aYJfEFdnrMBBYUARcYgPv1ob3J8v7k+X9cVje2WFG0xVMbm31uoTd", - "hleNrcusa1uo68hDjrBQ9OrQx0eKf//v02subLZU2ndn4EsLevixBV6e+lasvV/b7meDJ9jSLfoxroOU", - "/PWUd52WXce5Y71jHw686qmn3nE88lJIQg6P2yCcOKgF2X4TzvL2vWPZBvRVuBHaGI1np6dYlWKtjD2d", - "fZx/6MVvxA/fN+TxoblHPJl8RLpQWqyE5GXmYyPaftKzJyePZh//TwAAAP//4tel8/4TAQA=", + "H4sIAAAAAAAC/+x9a5PcNpLgX0HUboQeV+yWZNk71sXEXluyPb2WbYVa9t6upJtBkVlVmGYBHADsrrJO", + "//0CmQAJkmAVq7stj+P8SeoiHolEIpEvZH6Y5WpTKQnSmtmzD7OKa74BCxr/4nmuamkzUbi/CjC5FpUV", + "Ss6ehW/MWC3kajafCfdrxe16Np9JvoG2jes/n2n4Ry00FLNnVtcwn5l8DRvuBra7yrVuRtpmK5X5Ic5o", + "iPMXs497PvCi0GDMEMofZbljQuZlXQCzmkvDc/fJsGth18yuhWG+MxOSKQlMLZlddxqzpYCyMCdhkf+o", + "Qe+iVfrJx5f0sQUx06qEIZzP1WYhJASooAGq2RBmFStgiY3W3DI3g4M1NLSKGeA6X7Ol0gdAJSBieEHW", + "m9mztzMDsgCNu5WDuML/LjXAL5BZrldgZ+/nqcUtLejMik1iaece+xpMXVrDsC2ucSWuQDLX64R9XxvL", + "FsC4ZK+/ec4+++yzL91CNtxaKDyRja6qnT1eE3WfPZsV3EL4PKQ1Xq6U5rLImvavv3mO81/4BU5txY2B", + "9GE5c1/Y+YuxBYSOCRIS0sIK96FD/a5H4lC0Py9gqTRM3BNqfKebEs//m+5Kzm2+rpSQNrEvDL8y+pzk", + "YVH3fTysAaDTvnKY0m7Qt4+yL99/eDx//Ojjv7w9y/7b//n5Zx8nLv95M+4BDCQb5rXWIPNdttLA8bSs", + "uRzi47WnB7NWdVmwNb/CzecbZPW+L3N9iXVe8bJ2dCJyrc7KlTKMezIqYMnr0rIwMatl6diUG81TOxOG", + "VVpdiQKKueO+12uRr1nODQ2B7di1KEtHg7WBYozW0qvbc5g+xihxcN0IH7igf15ktOs6gAnYIjfI8lIZ", + "yKw6cD2FG4fLgsUXSntXmeMuK/ZmDQwndx/oskXcSUfTZbljFve1YNwwzsLVNGdiyXaqZte4OaW4xP5+", + "NQ5rG+aQhpvTuUfd4R1D3wAZCeQtlCqBS0ReOHdDlMmlWNUaDLteg137O0+DqZQ0wNTi75Bbt+3/cfHj", + "D0xp9j0Yw1fwiueXDGSuCihO2PmSSWUj0vC0hDh0PcfW4eFKXfJ/N8rRxMasKp5fpm/0UmxEYlXf863Y", + "1Bsm680CtNvScIVYxTTYWssxgGjEA6S44dvhpG90LXPc/3bajiznqE2YquQ7RNiGb//8aO7BMYyXJatA", + "FkKumN3KUTnOzX0YvEyrWhYTxBzr9jS6WE0FuVgKKFgzyh5I/DSH4BHyOHha4SsCJwwyCk4zywFwJGwT", + "NONOt/vCKr6CiGRO2E+eueFXqy5BNoTOFjv8VGm4Eqo2TacRGHHq/RK4VBaySsNSJGjswqPDMRhq4znw", + "xstAuZKWCwmFY84ItLJAzGoUpmjC/frO8BZfcANfPB2749uvE3d/qfq7vnfHJ+02NsroSCauTvfVH9i0", + "ZNXpP0E/jOc2YpXRz4ONFKs37rZZihJvor+7/QtoqA0ygQ4iwt1kxEpyW2t49k4+dH+xjF1YLguuC/fL", + "hn76vi6tuBAr91NJP71UK5FfiNUIMhtYkwoXdtvQP268NDu226Re8VKpy7qKF5R3FNfFjp2/GNtkGvNY", + "wjxrtN1Y8XizDcrIsT3sttnIESBHcVdx1/ASdhoctDxf4j/bJdITX+pf3D9VVbretlqmUOvo2F/JaD7w", + "ZoWzqipFzh0SX/vP7qtjAkCKBG9bnOKF+uxDBGKlVQXaChqUV1VWqpyXmbHc4kj/qmE5ezb7l9PW/nJK", + "3c1pNPlL1+sCOzmRlcSgjFfVEWO8cqKP2cMsHIPGT8gmiO2h0CQkbaIjJeFYcAlXXNqTVmXp8IPmAL/1", + "M7X4JmmH8N1TwUYRzqjhAgxJwNTwnmER6hmilSFaUSBdlWrR/HD/rKpaDOL3s6oifKD0CAIFM9gKY80D", + "XD5vT1I8z/mLE/ZtPDaK4kqWO3c5kKjh7oalv7X8LdbYlvwa2hHvGYbbqfSJ25qABifm3wXFoVqxVqWT", + "eg7Simv8F982JjP3+6TOvw8Si3E7TlyoaHnMkY6Dv0TKzf0e5QwJx5t7TthZv+/NyMaNsodgzHmLxbsm", + "HvxFWNiYg5QQQRRRk98erjXfzbyQmKGwNySTnwwQhVR8JSRCO3fqk2Qbfkn7oRDvjhDANHoR0RJJkI0J", + "1cucHvUnAzvL74BaUxsbJFEnqZbCWNSrsTFbQ4mCM5eBoGNSuRFlTNjwPYtoYL7WvCJa9l9I7BIS9Xlq", + "RLDe8uKdeCcmYY7YfbTRCNWN2fJB1pmEBLlGD4avSpVf/oWb9R2c8EUYa0j7OA1bAy9AszU368TB6dF2", + "O9oU+nYNkWbZIprqpFniS7Uyd7DEUh3DuqrqOS9LN/WQZfVWiwNPOshlyVxjBhuBBnOvOJKFnfQv9jXP", + "104sYDkvy3lrKlJVVsIVlE5pF1KCnjO75rY9/Dhy0GvwHBlwzM4Ci1bjzUxoYtONLUID23C8gTZOm6nK", + "bp+Ggxq+gZ4UhDeiqtGKECka5y/C6uAKJPKkZmgEv1kjWmviwU/c3P4TziwVLY4sgDa47xr8NfyiA7Rr", + "3d6nsp1C6YJs1tb9JjTLlaYh6Ib3k7v/ANdtZ6LO+5WGzA+h+RVow0u3ut6iHjTke1en88DJLLjl0cn0", + "VJhWwIhzYD8U70AnrDQ/4n94ydxnJ8U4SmqpR6AwoiJ3akEXs0MVzeQaoL1VsQ2ZMlnF88ujoHzeTp5m", + "M5NO3tdkPfVb6BfR7NCbrSjMXW0TDja2V90TQrarwI4GsshephPNNQUBb1TFiH30QCBOgaMRQtT2zq+1", + "r9Q2BdNXaju40tQW7mQn3DiTmf1XavvCQ6b0Yczj2FOQ7hYo+QYM3m4yZpxultYvd7ZQ+mbSRO+Ckaz1", + "NjLuRo2EqXkPSdi0rjJ/NhMeC2rQG6gN8NgvBPSHT2Gsg4ULy38FLBg36l1goTvQXWNBbSpRwh2Q/jop", + "xC24gc+esIu/nH3++Mlfn3z+hSPJSquV5hu22Fkw7L43yzFjdyU8SGpHKF2kR//iafBRdcdNjWNUrXPY", + "8Go4FPm+SPulZsy1G2Kti2ZcdQPgJI4I7mojtDNy6zrQXsCiXl2AtU7TfaXV8s654WCGFHTY6FWlnWBh", + "un5CLy2dFq7JKWyt5qcVtgRZUJyBW4cwTgfcLO6EqMY2vmhnKZjHaAEHD8Wx29ROs4u3Su90fRfmDdBa", + "6eQVXGllVa7KzMl5QiUMFK98C+ZbhO2q+r8TtOyaG+bmRu9lLYsRO4Tdyun3Fw39Zitb3Oy9wWi9idX5", + "eafsSxf5rRZSgc7sVjKkzo55ZKnVhnFWYEeUNb4FS/KX2MCF5Zvqx+XybqydCgdK2HHEBoybiVELJ/0Y", + "yJWkYL4DJhs/6hT09BETvEx2HACPkYudzNFVdhfHdtyatRES/fZmJ/PItOVgLKFYdcjy9iasMXTQVPdM", + "AhyHjpf4GW31L6C0/Bul37Ti67da1dWds+f+nFOXw/1ivDegcH2DGVjIVdkNIF052E9Sa/xNFvS8MSLQ", + "GhB6pMiXYrW2kb74Sqtf4U5MzpICFD+Qsah0fYYmox9U4ZiJrc0diJLtYC2Hc3Qb8zW+ULVlnElVAG5+", + "bdJC5kjIIcY6YYiWjeVWtE8IwxbgqCvntVttXTEMQBrcF23HjOd0QjNEjRkJv2jiZqgVTUfhbKUGXuzY", + "AkAytfAxDj76AhfJMXrKBjHNi7gJftGBq9IqB2OgyLwp+iBooR1dHXYPnhBwBLiZhRnFllzfGtjLq4Nw", + "XsIuw1g/w+5/97N58BvAa5Xl5QHEYpsUevv2tCHU06bfR3D9yWOyI0sdUa0Tbx2DKMHCGAqPwsno/vUh", + "Guzi7dFyBRpDSn5Vig+T3I6AGlB/ZXq/LbR1NRLB7tV0J+G5DZNcqiBYpQYrubHZIbbsGnVsCW4FESdM", + "cWIceETwesmNpTAoIQu0adJ1gvOQEOamGAd4VA1xI/8cNJDh2Lm7B6WpTaOOmLqqlLZQpNaAHtnRuX6A", + "bTOXWkZjNzqPVaw2cGjkMSxF43tkeQ0Y/+C28b96j+5wcehTd/f8LonKDhAtIvYBchFaRdiNo3hHABGm", + "RTQRjjA9ymlCh+czY1VVOW5hs1o2/cbQdEGtz+xPbdshcZGTg+7tQoFBB4pv7yG/JsxS/PaaG+bhCC52", + "NOdQvNYQZncYMyNkDtk+ykcVz7WKj8DBQ1pXK80LyAoo+S4RHECfGX3eNwDueKvuKgsZBeKmN72l5BD3", + "uGdoheOZlPDI8AvL3RF0qkBLIL73gZELwLFTzMnT0b1mKJwruUVhPFw2bXViRLwNr5R1O+7pAUH2HH0K", + "wCN4aIa+OSqwc9bqnv0p/guMn6CRI46fZAdmbAnt+EctYMQW7N84Reelx957HDjJNkfZ2AE+MnZkRwzT", + "r7i2IhcV6jrfwe7OVb/+BEnHOSvAclFCwaIPpAZWcX9GIaT9MW+mCk6yvQ3BHxjfEssJYTpd4C9hhzr3", + "K3qbEJk67kKXTYzq7icuGQIaIp6dCB43gS3Pbblzgppdw45dgwZm6gWFMAz9KVZVWTxA0j+zZ0bvnU36", + "Rve6iy9wqGh5qVgz0gn2w/empxh00OF1gUqpcoKFbICMJASTYkdYpdyuC//8KTyACZTUAdIzbXTNN9f/", + "PdNBM66A/ZeqWc4lqly1hUamURoFBRQg3QxOBGvm9MGJLYaghA2QJolfHj7sL/zhQ7/nwrAlXIc3g65h", + "Hx0PH6Id55UytnO47sAe6o7beeL6QMeVu/i8FtLnKYcjnvzIU3byVW/wxtvlzpQxnnDd8m/NAHoncztl", + "7TGNTIv2wnEn+XK68UGDdeO+X4hNXXJ7F14ruOJlpq5Aa1HAQU7uJxZKfn3Fyx+bbvgeEnJHozlkOb7i", + "mzgWvHF96OGfG0dI4Q4wBf1PBQjOqdcFdTqgYraRqmKzgUJwC+WOVRpyoPduTnI0zVJPGEXC52suV6gw", + "aFWvfHArjYMMvzZkmtG1HAyRFKrsVmZo5E5dAD5MLTx5dOIUcKfS9S3kpMBc82Y+/8p1ys0c7UHfY5B0", + "ks1noxqvQ+pVq/EScrrvNidcBh15L8JPO/FEVwqizsk+Q3zF2+IOk9vcX8dk3w6dgnI4cRTx234cC/p1", + "6na5uwOhhwZiGioNBq+o2Exl6Ktaxm+0Q6jgzljYDC351PWvI8fv9ai+qGQpJGQbJWGXTEsiJHyPH5PH", + "Ca/Jkc4osIz17esgHfh7YHXnmUKNt8Uv7nb/hPY9VuYbpe/KJUoDThbvJ3ggD7rb/ZQ39ZPysky4Fv0L", + "zj4DMPMmWFdoxo1RuUCZ7bwwcx8VTN5I/9yzi/5XzbuUOzh7/XF7PrQ4OQDaiKGsGGd5KdCCrKSxus7t", + "O8nRRhUtNRHEFZTxcavl89AkbSZNWDH9UO8kxwC+xnKVDNhYQsJM8w1AMF6aerUCY3u6zhLgnfSthGS1", + "FBbn2rjjktF5qUBjJNUJtdzwHVs6mrCK/QJasUVtu9I/PlA2VpSld+i5aZhavpPcshK4sex7Id9scbjg", + "9A9HVoK9VvqywUL6dl+BBCNMlg42+5a+Yly/X/7ax/hjuDt9DkGnbcaEmVtmJ0nK/7n/78/enmX/zbNf", + "HmVf/o/T9x+efnzwcPDjk49//vP/7f702cc/P/j3f03tVIA99XzWQ37+wmvG5y9Q/YlC9fuwfzL7/0bI", + "LElkcTRHj7bYfUwV4QnoQdc4ZtfwTtqtdIR0xUtRON5yE3Lo3zCDs0ino0c1nY3oGcPCWo9UKm7BZViC", + "yfRY442lqGF8ZvqhOjol/dtzPC/LWtJWBumb3mGG+DK1nDfJCChP2TOGL9XXPAR5+j+ffP7FbN6+MG++", + "z+Yz//V9gpJFsU3lEShgm9IV40cS9wyr+M6ATXMPhD0ZSkexHfGwG9gsQJu1qD49pzBWLNIcLjxZ8jan", + "rTyXFODvzg+6OHfec6KWnx5uqwEKqOw6lb+oI6hhq3Y3AXphJ5VWVyDnTJzASd/mUzh90Qf1lcCXITBV", + "KzVFG2rOARFaoIoI6/FCJhlWUvTTe97gL39z5+qQHzgFV3/OVETvvW+/fsNOPcM09yilBQ0dJSFIqNL+", + "8WQnIMlxs/hN2Tv5Tr6AJVoflHz2Thbc8tMFNyI3p7UB/RUvuczhZKXYs/Ae8wW3/J0cSFqjiRWjR9Os", + "qhelyNllrJC05EnJsoYjvHv3lpcr9e7d+0FsxlB98FMl+QtNkDlBWNU286l+Mg3XXKd8X6ZJ9YIjUy6v", + "fbOSkK1qMpCGVEJ+/DTP41Vl+ikfhsuvqtItPyJD4xMauC1jxqrmPZoTUPyTXre/Pyh/MWh+HewqtQHD", + "/rbh1Vsh7XuWvasfPfoMX/a1ORD+5q98R5O7CiZbV0ZTUvSNKrhwUisxVj2r+CrlYnv37q0FXuHuo7y8", + "QRtHWTLs1nl1GB4Y4FDtAponzqMbQHAc/TgYF3dBvUJax/QS8BNuYfcB9q32K3o/f+PtOvAGn9d2nbmz", + "nVyVcSQedqbJ9rZyQlaIxjBihdqqT4y3AJavIb/0GctgU9ndvNM9BPx4QTOwDmEolx29MMRsSuigWACr", + "q4J7UZzLXT+tjaEXFTjoa7iE3RvVJmM6Jo9NN62KGTuoSKmRdOmINT62foz+5vuosvDQ1GcnwcebgSye", + "NXQR+owfZBJ57+AQp4iik/ZjDBFcJxBBxD+Cghss1I13K9JPLU/IHKQVV5BBKVZikUrD+59Df1iA1VGl", + "zzzoo5CbAQ0TS+ZU+QVdrF6911yuwF3P7kpVhpeUVTUZtIH60Bq4tgvgdq+dX8YJKQJ0qFJe48trtPDN", + "3RJg6/ZbWLTYSbh2WgUaiqiNj14+GY8/I8ChuCE8oXurKZyM6roedYmMg+FWbrDbqLU+NC+mM4SLvm8A", + "U5aqa7cvDgrls21SUpfofqkNX8GI7hJ77ybmw+h4/HCQQxJJUgZRy76oMZAEkiBT48ytOXmGwX1xhxjV", + "zF5AZpiJHMTeZ4RJtD3CFiUKsE3kKu091x0vKmUFHgMtzVpAy1YUDGB0MRIfxzU34ThivtTAZSdJZ79i", + "2pd9qenOo1jCKClqk3gu3IZ9DjrQ+32CupCVLqSii5X+CWnlnO6FzxdS26EkiqYFlLCihVPjQChtwqR2", + "gxwcPy6XyFuyVFhiZKCOBAA/BzjN5SFj5Bthk0dIkXEENgY+4MDsBxWfTbk6BkjpEz7xMDZeEdHfkH7Y", + "R4H6ThhVlbtcxYi/MQ8cwKeiaCWLXkQ1DsOEnDPH5q546dic18XbQQYZ0lCh6OVD86E3D8YUjT2uKbry", + "j1oTCQk3WU0szQag06L2HogXapvRC+WkLrLYLhy9J98u4Hvp1MGkXHT3DFuoLYZz4dVCsfIHYBmHI4AR", + "2V62wiC9Yr8xOYuA2Tftfjk3RYUGScYbWhtyGRP0pkw9IluOkcv9KL3cjQDomaHaWg3eLHHQfNAVT4aX", + "eXurzdu0qeFZWOr4jx2h5C6N4G9oH+smhPtLm/hvPLlYOFGfJBPe0LJ0mwyF1LmirIPHJCjsk0MHiD1Y", + "fdWXA5No7cZ6dfEaYS3FShzzHTolh2gzUAIqwVlHNM0uU5ECTpcHvMcvQrfIWIe7x+XuQRRAqGEljIXW", + "aRTign4LczzH9MlKLcdXZyu9dOt7rVRz+ZPbHDt2lvnJV4AR+Euhjc3Q45Zcgmv0jUEj0jeuaVoC7YYo", + "UrEBUaQ5Lk57CbusEGWdplc/73cv3LQ/NBeNqRd4iwlJAVoLLI6RDFzeMzXFtu9d8Eta8Et+Z+uddhpc", + "UzexduTSneN3ci56DGwfO0gQYIo4hrs2itI9DDJ6cD7kjpE0GsW0nOzzNgwOUxHGPhilFp69j938NFJy", + "LVEawPQLQbVaQRHSmwV/mIySyJVKrqIqTlW1L2feCaPUdZh5bk/SOh+GD2NB+JG4nwlZwDYNfawVIOTt", + "yzpMuIeTrEBSupK0WSiJmjjEH1tEtrpP7AvtPwBIBkG/6Tmz2+hk2qVmO3EDSuCF10kMhPXtP5bDDfGo", + "m4+FT3cyn+4/Qjgg0pSwUWGTYRqCEQbMq0oU257jiUYdNYLxo6zLI9IWshY/2AEMdIOgkwTXSaXtQ629", + "gf0Udd5Tp5VR7LUPLHb0zXP/AL+oNXowOpHNw7ztja42ce3f/XxhleYr8F6ojEC61RC4nGPQEGVFN8wK", + "CicpxHIJsffF3MRz0AFuYGMvJpBugsjSLppaSPvF0xQZHaCeFsbDKEtTTIIWxnzyb4ZeriDTR6ak5kqI", + "tuYGrqrkc/3vYJf9zMvaKRlCmzY817udupfvEbt+tfkOdjjywahXB9iBXUHL02tAGkxZ+ptPJkpgfc90", + "UvyjetnZwiN26iy9S3e0Nb4owzjxt7dMp2hBdym3ORhtkISDZcpuXKRjE9zpgS7i+6R8aBNEcVgGieT9", + "eCphQgnL4VXU5KI4RLtvgJeBeHE5s4/z2e0iAVK3mR/xAK5fNRdoEs8YaUqe4U5gz5Eo51Wl1RUvMx8v", + "MXb5a3XlL39sHsIrPrEmk6bsN1+fvXzlwf84n+UlcJ01loDRVWG76nezKirjsP8qoWzf3tBJlqJo85uM", + "zHGMxTVm9u4ZmwZFUdr4mego+piLZTrg/SDv86E+tMQ9IT9QNRE/rc+TAn66QT78iosyOBsDtCPB6bi4", + "aZV1klwhHuDWwUJRzFd2p+xmcLrTp6OlrgM8Cef6EVNTpjUO6RNXIivywT/8zqWnb5TuMH//MjEZPPTr", + "iVVOyCY8jsRqh/qVfWHqhJHg9bfV39xpfPgwPmoPH87Z30r/IQIQf1/431G/ePgw6T1MmrEck0ArleQb", + "eNC8shjdiE+rgEu4nnZBn11tGslSjZNhQ6EUBRTQfe2xd62Fx2fhfymgBPfTyRQlPd50QncMzJQTdDH2", + "ErEJMt1QyUzDlOzHVOMjWEdayOx9SQZyxg6PkKw36MDMTCnydGiHXBjHXiUFU7rGDBuPWGvdiLUYic2V", + "tYjGcs2m5EztARnNkUSmSaZtbXG3UP5411L8owYmCqfVLAVovNd6V11QDnDUgUCatov5gclP1Q5/GzvI", + "Hn9TsAXtM4Ls9d+9aHxKYaGpoj9HRoDHMw4Y957obU8fnprpNdu6G4I5TY+ZUjo9MDrvrBuZI1kKXZhs", + "qdUvkHaEoP8okQgjOD4Fmnl/AZmK3OuzlMap3FZ0b2c/tN3TdeOxjb+1LhwW3VQdu8llmj7Vx23kTZRe", + "k07X7JE8poTFEQbdpwEjrAWPVxQMi2VQQvQRl3SeKAtE54VZ+lTGbzlPafz2VHqYB+9fS3694KkaMU4X", + "cjBF29uJk7KKhc5hA0yT44BmZ1EEd9NWUCa5CnTrgxhmpb2hXkPTTtZoWgUGKSpWXeYUplAalRimltdc", + "UhVx14/4le9tgFzwrte10pgH0qRDugrIxSZpjn337m2RD8N3CrESVCC7NhBVYPYDMUo2iVTkq1g3mTs8", + "as6X7NE8KgPvd6MQV8KIRQnY4jG1WHCD12XjDm+6uOWBtGuDzZ9MaL6uZaGhsGtDiDWKNbonCnlNYOIC", + "7DWAZI+w3eMv2X0MyTTiCh44LHohaPbs8ZcYUEN/PErdsr7A+T6WXSDPDsHaaTrGmFQawzFJP2o6+nqp", + "AX6B8dthz2mirlPOErb0F8rhs7Thkq8g/T5jcwAm6ou7ie78Hl4keQPAWK12TNj0/GC5408jb74d+yMw", + "WK42G2E3PnDPqI2jp7a8Mk0ahqNa/75eVIArfMT41yqE//VsXZ9YjeGbkTdbGKX8A/poY7TOGafkn6Vo", + "I9NDvU52HnILYwGtpm4W4cbN5ZaOsiQGqi9ZpYW0aP+o7TL7k1OLNc8d+zsZAzdbfPE0UYiqW6tFHgf4", + "J8e7BgP6Ko16PUL2QWbxfdl9qWS2cRyleNDmWIhO5WigbjokcywudP/QUyVfN0o2Sm51h9x4xKlvRXhy", + "z4C3JMVmPUfR49Er++SUWes0efDa7dBPr196KWOjdKpgQHvcvcShwWoBV/hiLr1Jbsxb7oUuJ+3CbaD/", + "beOfgsgZiWXhLCcVgcijue+xvJPif/6+zXyOjlV6idizASqdsHZ6u90njjY8zurW999SwBh+G8HcZLTh", + "KEOsjETfU3h90+e3iBfqg0R73jE4Pv4b004HRzn+4UME+uHDuReD//ak+5nY+8OH6QTESZOb+7XFwm00", + "Yuyb2sOvVMIAFqoWNgFFPj9CwgA5dkm5D44JLvxQc9atEPfppYi7ed+VjjZNn4J3797il4AH/KOPiN+Y", + "WeIGtq8Uxg97t0JmkmSK5nsU587ZV2o7lXB6d1Agnn8CFI2gZKJ5DlcyqACadNcfjBeJaNSNuoBSOSUz", + "LgoU2/N/P3h2i5/vwXYtyuLnNrdb7yLRXObrZJTwwnX8K8nonSuYWGWyzsiaSwllcjjSbf8adOCElv53", + "NXWejZAT2/Yr0NJye4trAe+CGYAKEzr0Clu6CWKsdtNmNWkZypUqGM7TFrVomeOwlHOqhGbifTMOu6mt", + "j1vFt+A+4dBSlBiGmfYbY8tMczuSQAvrnYf6Qm4cLD9uyMxAo4NmXGzwYjZ8U5WAJ/MKNF9hVyWh1x1T", + "qOHIUcUKZir3CVtiwgrFbK0lU8tltAyQVmgod3NWcWNokEduWbDFuWfPHj96lDR7IXYmrJSwGJb5Y7uU", + "x6fYhL74IktUCuAoYA/D+rGlqGM2dkg4vqbkP2owNsVT8QO9XEUvqbu1qZ5kU/v0hH2LmY8cEXdS3aO5", + "MiQR7ibUrKtS8WKOyY3ffH32ktGs1IdKyFM9yxVa67rkn3SvTE8wGjI7jWTOmT7O/lQebtXGZk35yVRu", + "QteiLZApejE3aMeLsXPCXpAJtSngT5MwTJGtN1BE1S5JiUficP+xludrtE12JKBxXjm9EGtgZ63nJnp9", + "2FQ/Qobt4Pa1WKkU65wpuwZ9LQzgi3y4gm46xCY3qLeNh/SI3eXpWkqilJMjhNGm1tGxaA/AkSQbggqS", + "kPUQf6RliuoxH1uX9gJ7pd9i9Irc9rz+IbleSLHNvvfOhZxLJUWOpRBSkjSmbpvmppxQNSLtXzQzf0IT", + "hytZWrd5C+yxOFpsNzBCj7ihyz/66jaVqIP+tLD1JddWYI3nbFDMQ6Vr7xAT0oCvZuWIKOaTSieCmpIP", + "IZoAiiPJCLMyjVg4v3HffvD2b0yKcSkkWro82rx+Ri6r0gj0TEsmLFspMH493dc85q3rc4JZGgvYvj95", + "qVYivxArHIPC6NyyKWZ0ONRZiCD1EZuu7XPX1ufOb37uhIPRpGdV5Scdr4OeFCTtVo4iOBW3FAJJIuQ2", + "48ej7SG3vaHfeJ86QoMrjFqDCu/hAWE0tbS7o3ztdEuiKGzB6EVlMoGukAkwXgoZXKjpCyJPXgm4MXhe", + "R/qZXHNLusMknvYGeDnyAAJfKJMP/rZD9SsHOJTgGsMc49vYlgEfYRxNg1bi53LHwqFw1B0JE8952YRO", + "J4p6o1TlhagCHxf1ynynGIdj3Fl4MtlB18Hne013rMZx7E00lqNwURcrsBkvilRqq6/wK8Ov4ZEYbCGv", + "myJUzevAbo7yIbX5iXIlTb3ZM1docMvporr5CWqIa/eHHcZMO4sd/puqwDS+Mz5o+uhXuSFCujguMf/w", + "lXFK6nU0nRmxyqZjAu+U26OjnfpmhN72v1NKD891/yle4/a4XLxHKf72tbs44sS9g/h0ulqavLoYC67w", + "e0h41GSE7HIlvMoGdcYw6gE3L7FlPeBDwyTgV7wceQkf+0rofiX/wdh7+Hw0fQO3Pj2X5WwvCxpNeUSx", + "wj3vy9CFOBYfTOHBd+e18Gvdi9Bx3913HU8dxYi1zGLUQ3czJ1q7wcd60b67GkuREOp04Pe4HoiP4pn7", + "NPBwJVQdoq9CDHRQCelXn4KnU/djZP3JlwW/tddi1MfyxtevpWV6nfy7n8kLy0Bavfsn8LgMNr1fVCYh", + "7ZJ5qm3CmtKHk0ohdm7FKTVsUuVSvGwYbGXEWjq0NCg/MyCrF1PEgQE+Ps5n58VRF2aq5M6MRkkdu5di", + "tbaYsf8vwAvQrw5UJGirEOARq5QRbQXS0g3mU8CucbiTqY8NHAGLuKLCcKwQhHoFucWys21wnQY4pr6C", + "myw4ff6oTDCuTjdvMnxBgn1VCIa1Zg/c8YPESVHyL6rTeTI95/5ZE0JNL8CuuWnTtfTeTE9+ublcQo5Z", + "kfcmqvrPNcgoCdI82GUQlmWUt0o075gwr/fxVscWoH15pPbCE9XXuTU4Y+/YL2F3z7AONSQLhzaP+G6S", + "OBgxQC6wkEN6zJDso8aEaSgDsRBCgn0q5rY4xmjO5yjt2g3nCiTpLo42FdueKdNFzyfN5boelfYRn+SM", + "5bIa1kwe1z9eYIlq4wPkeJN4ONbS2fmwcM61T1yMacUa30lIYQwm/BZyCNIspbj09QMQK+Spuua6CC3u", + "JCkU3U0iDfSymVm0DziGQQ6JUgz4FiovlRMjsrEHZd03E03A4T1DkaFtAh+EawlaQ9G4REplILMqPPjY", + "B8c+VFD4642QYEbLHxFwo6mvX7e5vbEMHMdU19xHvcYLZBo23EGnowzc43PuQ/Zz+h4e4YcyYActTA29", + "Hq5HG57uCDNAYkz1S+Zvy8OP+29ibBJSgs6C56mfjlt2M7Jh3s2izumCjg9GY5CbnDtnDytJ2mny4Sp7", + "OkL0SP4SdqekBIVCvmEHY6BJciLQo4SjvU2+U/ObScG9uhPwfts8cpVSZTbi7Dgf5hDvU/ylyC8BcwA2", + "Ie4jNdrZfbSxN97s6/Uu5MyuKpBQPDhh7EzSo6Lg2O6WF+xNLu/ZffNvcdaiprT+3qh28k6mX2dgwn19", + "S24WhtnPwww4VnfLqWiQAxmqt3Is5OYak/N3q3ieTNXKh67mfhX5lqgIipRMckEeq+d40FOGI0yBEOXq", + "QEcmZ97TxUypUrG8N0nT4IZKYyqeDAGyIKdkC2ig8IMnEZCsi544hZT6zie9U0umoXUi3zT737CEe0qj", + "78/czNLld0uloVOM3fWmTJ/NwxdMo4n/WQirud7dJEffoIT8wHoyiuWD4VhNJFa7kDYaa4jDslTXGTKr", + "rKlzkVJtXTvTvYxD0bW2nzvVC4jiurjxgtqOrXnBcqU15HGP9HtPgmqjNGSlwjCvlAd6aZ3cvcFHXpKV", + "asVUlasCqF5MmoLG5qql5Cg2QRRVk0QB0Q6+FqY+ER1PnNLdqeRHylDUWh1ROz8HerneZnWiRWfkyxyJ", + "WAbjszh5DFHjIbx7av8fVanlHMMYrwTGunQf7ZP0Wbk7pslkEJ+5izjNELNrrerVOkrozK5FWQaDgdsG", + "XXsFNB7lJ1NjOBK+2HJTPGUbZazX7Ggk0wzVhnjdz5W0WpVl1whEIvHKW7a/59uzPLcvlbpc8PzyAeqR", + "UtlmpcU8vGfuB+O1M+leKq/uhZdR+fDDqXGpHYameSKZzJB6LOXoQuoRmO8Pc6zDNu6z4cL66+oyr7Ta", + "cCYZt2oj8jQN/76i20Zj0lIsIZkjjGoZUlYHbIaMOr4cmmAGZElDNIPkyWJsZ8zzNO/URebh/osSb39c", + "tgR/SYxcTEM+6aWWLB+VrXoAIKT01NjWmgogxpJPw1XUilIToEu6D+hELo6RP7eDzY1w50BZuBVQg2jD", + "BsD7pOzPKZcbRS4u1DZ8f9Ame7sR8B/3U3mHeYyFVF20pKUpqCokhhnhCOmU0nvjj97gM/PF1Cikpljt", + "xBs1AmA8LqkDw6TopGPBWHJRQpGlah2eNzaheaTZ+qdQ/RLkwnhOnvM6lBp0Y9cafKISEql1199UcUdK", + "qmk+tNzKArZA7yh+Aa2ohuA88ndASSUGe8q3qrISrqATruWzp9Qo2okrCH1N05kVABV6//o2qVQcUnyX", + "9wwVfu1ZFMkyBbtJywUhlnaKHTBLJI0oW5nRMTFTj5KD6EoUNe/gzxwrcnTNbu4oJ1A1kMmzoLdNneYn", + "GuF1GOAs9E+JMgET76fxoaNZUBp1+xjQwbjE2oydepkOS4xTAzUODZytaByfROIt3zAVv5bjBsAhybfq", + "zcR9EkpGiP16CzlKNd24u9vjhOFgzPTSfo2K4LrZ4Zsbkn8TGt5LwqPjpVQNA/6h2h5LTaALL7BjAyw6", + "LZ3Y66RmLCfo+b/nf3O2qMNATq+m6oaxBvcCgscOM5E3zgov0IrmQgvxhXOfiLKvlIsosnrDd0xp/Mfp", + "a/+oeSmWOzyhBH7oxsyaOxLyLkLyXft4RTfxfsFkHgALdgEVpqJ1i6ljRsPt3CgR0O4KDGVoFNvwS4i3", + "Ad3yxHly61iOqRcbYQxedr3tHGLBLz4kE9nwItaRMaVht+B3SHLrev/P9tVWPFXIRFaVPA+1LH0xnY5B", + "nOrVBuKya9jsf9Y3VI8DCTQ1cFui1eEdeHED496RkRupWPmxQiEdsAe1QQc1Um61jIk2yl41iD0PIict", + "5a53YWp8yADouKLgIfDjAoufBv/JbKNjy5gC/j8L3kdKqsbwUvXUT4DlTq6IBKxkV12obaZhaQ6FQpBh", + "1SnCus0yEYyTQuYauKHYkPMfvcrWJtMU0qmQFL3YeN+aUQpYCtkySyGr2iY0AMypKXcRwmLzNKJ1xNkz", + "JiU4MeyKlz9egdaiGNs4dzqo+GBczCCY5H3fhPLf3KnDAYRptR98SQjtS7WombvAqVwSBRYay2XBdRE3", + "F5LloN29z675ztzc9+Gg1bWTLw54P3gkzXTft0d+ECRtAqTcefflLT0TDYD8Dl0UE1wLGMGacCuQUcSq", + "EU/CEIZ0Pg6+zUq1wvdlIwTos5ai74eUFSXRYEvy0HHzGPEL7J8GE7b7g28Vzjpliv3n7EdEHSo8P0lh", + "9540sqb1H/xRRCYdhED/ctWGhdPmDOk/9UbTp+WI32kG4S48Ygh7TeEhNB+MeDK6FtyRXUQHuX/gG5tr", + "pxfC6vrgUy9BSYfNULc1ewK/wbRBzjz3gTtDo89AKSakzP072iNtQmRJDvfACHhUtdyfre60TTCFG+eY", + "6mH7X85mlaqyfEo0INV0KLxB20PahXGEPiJz9ci6m8AJ01Q56WTE6ZQ7ObaA2mi5lUN+mSrfp2SPGTRG", + "OGjXWK6WyMuopjfaYfCNR2O8mPdfH3UNNg2TYJxpyGuNBs1rvjtckGokl/DFX84+f/zkr08+/4K5BqwQ", + "KzBtPupeQac2YkzIvp3l08aIDZZn05sQ3qUT4oKnLDy3aTbFnzXitqZNNjkoZ3WMJTRxASSOY6KQ0I32", + "Csdpg77/ubYrtcg737EUCn79PdOqLNP1ABrRLWHqT+1WZOx3En8F2ghjHSPs+uqEbWNlzRrNcZgV9ory", + "jCiZ+7T9DRUIOxKMk1rIWKgl8jN89ev9Gwy2Vel5Ffkk9q3L60VkEcPgDIzfWACrVOVFabFkKYjwbYmO", + "3lx6QyOGd0bRkw2zpTjKFCH6mOQ06cWllPdz+26ZT5vm9G4TE+JFOJQ3IM0xS/r4i/abcJLWlP5Pwz8S", + "T/TvjGs0y/01eEVSP7hZufZJoA2fayfIAwEYeYfZeUEXPSGKUtRqssqj/T64Ovvix/etC/TggwGEJHQ4", + "AF78sLJt18S4e3B+41yv3zdIiZbyfowSOss/9FYzsN7mIom2yBsprAVDbEkNxcLoIa553rxvHdFKBs9g", + "tVKWOc20LBPPZ8lugmcqJhynEugrXn56rvGN0MaeIT6geD3+aCZ+QxkjmVBpbpbB7SWfNHf0XvLuppav", + "8Mnuf4Lbo+Q954fy7uLBbYZWL6xlvgq3Ar0CZtc4JoUDPf6CLXwZhkpDLkzfDX0dhJPmySBosfShl7C1", + "B94oHlrnz8regoyXIWaE/RC5kxSa7VoI2yP6GzOVkZObpPIU9Q3IIoG/FI+Ky7YeuC5umbL/ZglBotRe", + "RyYEGRaknbo8SnrhLp3awHCdk2/rDm4TF3W7tqnZbCZn/n/37q1dTElCk87S77pjFpw7Sdd/VLL+XyH/", + "DeHIj+HnTVHMz2MZUSnr50jW5t5+1KI8GCDSycH9cT5bgQQjDGaZ/quvKvJp79IAAb3JHx5VgvU2iUQI", + "MYm1diaPpoqya09IrO27JbIh43u3vNbC7rCibDCgib8mM/V822R98FlDGt+Vv/usuoSmqnebI6I24Xb9", + "VvES7yNyqUl3C6nyhH1NuZ/9QfnzvcW/wWd/elo8+uzxvy3+9OjzRzk8/fzLR4/4l0/54y8/ewxP/vT5", + "00fwePnFl4snxZOnTxZPnzz94vMv88+ePl48/eLLf7vn+JADmQANSd+fzf53dlauVHb26jx744BtccIr", + "8R24vUFdeamw4qFDao4nETZclLNn4af/FU7YSa427fDh15mv3DNbW1uZZ6en19fXJ3GX0xU+Cs+sqvP1", + "aZgH69B15JVX5000OcW94I621mPcVE8KZ/jt9dcXb9jZq/OTlmBmz2aPTh6dPPZFjyWvxOzZ7DP8CU/P", + "Gvf9FDMvnhqfVP20qnxa9aSb7LWvxdOluNAZgW3ycrvdpnTdPjm6iSsenxdIW3aY0h0rc2EYFAL45NGj", + "sCte5omunlN8sfDsw2xa/fPhZLjz/WwLi3r1ysEcknw0ad+8c8LjDP2FhLBmv0gF5iuDlnUtrriF2fuP", + "81lVJ9D5NT5HMPtwNo9ShRM0qiwajA8w+qr+/wSjH+ezU88nZ88+uL/WwEtM/+P+2DhCzcMnDbzY+f+b", + "a75agT7x63Q/XT05DRLx6Qef1+Hjvm+ncTTQ6YdO+oviQM8Q7XKoyemHUNh3/4Cdoq4+zjDqMBHQfc1O", + "F1jMZ2pTiFc3vhSkeXP6AZXB0d9PvUUv/RGVcuL2pyGNzEhLShiQ/thB4Qe7dQvZP5xrE42Xc5uv6+r0", + "A/4HyTZaEeUfPbVbeYpBA6cfOojwnweI6P7edo9bXG1UAQE4tVxSNeR9n08/0L/RRLCtQAunEWHOH/8r", + "5WY7xaJ4u+HPO5knfxyuo5OXat/NUmu0TwoTImm66ayS10c/R5a5LbOblnGjn5lrKOwNb/V9K/s4nz29", + "Q67czWeaAOYrXrDw/Brnfvzp5j6XFNHrxBwSxxCCp58Ogs72se9gx35Qln2DdoOP89nnn3InzqXTYnjJ", + "sGVUlHh4RH6Sl1Jdy9DSyfH1ZsP1bvLx6V+jTg5smskVCSqKnuR3j9pZUQyInvQZMPYrhbfrGMY2ZlV5", + "j2GLtFadE9ItYWgPGqDqDdXm7iW5o1RMQZCQqoBZrGhZXcPHW/KEXqwR1/Y8Yd5EOz0G+S9DGfEI1GTG", + "tn4kBo08VMUPkXBb6b6Njf+Dp/zBUxqe8vmjzz7d9Begr0QO7A1sKqW5FuWO/SSbRxc35nFnRZFMc9k9", + "+gd53Hy2zXJVwApk5hlYtlDFztcNmXUmuASy3AwEmdNg6ehoDCPcM9hQUtJKGwo8e/Y25aL3paerelGK", + "nJGVF80cToePrBBN3sEu85tH2zpgP4nc1qwQZd28gbfXyr8xHV4o7H6cGcL8Q+PFgwdR2B27FrJQ11iS", + "H8H9Rw3I5z28YZpZAsAo3nRYxqV1XjkAB2CNzYderynY2TP5S36zuUt+7NTvb3llHbxMm7xe/3Hx4w/R", + "SzSyNFBwCr6DItLFoHWtMBj7mmN0IpV7e042oHLHpEL/Vm06laZO/riH/uD9t+f93zaJXqnGlMXiMUOW", + "FN0FJ5ME3iRv/9D509stZhQanErP6n5nnK2wPuDwglrs2PmLgfZK3fpXwlc7bNq7FRL8vg/iUYx/hL3s", + "E2ncQlbKNgHStKg/hMw/hMxbKa6TD88U3TVpWaKqnXygj81DAc7OIxRMsYzhRgNQptifftPjeycbP7Rt", + "pWxZlAoaChZ9oMwEfTT/wSL+YBG3YxHfQuIw4qn1TCNBdMfZuqYyDExAU3TC/YLUEZrXJdfRY9BDJuwz", + "HDGtCv4qXONTG+ySuCJ7HcawCwreTGzg3drw/mB5f7C83w/LOzvMaLqCya2tXpew2/CqsXWZdW0LdR15", + "yBEWCrwe+vhI8e//fXrNhc2WSvvCInxpQQ87W+Dlqa8i3Pu1Ldw3+ILVCKMf4xReyV9Peddp2XWcO9Y7", + "1nHgVU999Y7jkUbh/Xz43MaPxfFYyPabSKy37x3LNqCvwo3Qhhc9Oz3FhCprZezp7OP8Qy/0KP74viGP", + "D8094snkI9KF0mIlJC8zHxvRlkKfPTl5NPv4/wIAAP//Vd+WO7oZAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 409fd4ca65..4548badf2c 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -177,233 +177,237 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAD//23D8FrbHAEA", + "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", + "8aePjk8nH/5/AAAA//9iOfQe9x8BAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 3d581dbd78..f338c38b1f 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -26,8 +26,10 @@ import ( "math" "net/http" "os" + "runtime" "strconv" "strings" + "sync/atomic" "time" "github.com/labstack/echo/v4" @@ -2083,3 +2085,66 @@ func (v2 *Handlers) SetBlockTimeStampOffset(ctx echo.Context, offset uint64) err } return ctx.NoContent(http.StatusOK) } + +// savedBlockingRate is the current blocking rate +var savedBlockingRate atomic.Int32 + +// GetDebugSettingsProf returns the current mutex and blocking rates. +func (v2 *Handlers) GetDebugSettingsProf(ctx echo.Context) error { + mutexRate := uint64(runtime.SetMutexProfileFraction(-1)) + blockingRate := uint64(savedBlockingRate.Load()) + + response := model.DebugSettingsProf{ + MutexRate: &mutexRate, + BlockRate: &blockingRate, + } + + return ctx.JSON(http.StatusOK, response) +} + +// PutDebugSettingsProf sets the mutex and blocking rates and returns the old values. +func (v2 *Handlers) PutDebugSettingsProf(ctx echo.Context) error { + req := ctx.Request() + buf := new(bytes.Buffer) + req.Body = http.MaxBytesReader(nil, req.Body, 128) + _, err := buf.ReadFrom(ctx.Request().Body) + if err != nil { + return badRequest(ctx, err, err.Error(), v2.Log) + } + data := buf.Bytes() + + var opts model.DebugSettingsProf + err = decode(protocol.JSONStrictHandle, data, &opts) + if err != nil { + return badRequest(ctx, err, err.Error(), v2.Log) + } + + var response model.DebugSettingsProf + + // validate input fiest + if opts.MutexRate != nil && *opts.MutexRate > math.MaxInt32 { + err = errors.New("blocking rate cannot be larger than max int32 value") + return badRequest(ctx, err, err.Error(), v2.Log) + } + if opts.BlockRate != nil && *opts.BlockRate > math.MaxInt32 { + err = errors.New("blocking rate cannot be larger than max int32 value") + return badRequest(ctx, err, err.Error(), v2.Log) + } + + if opts.MutexRate != nil { + newMutexRate := int(*opts.MutexRate) + oldMutexRate := uint64(runtime.SetMutexProfileFraction(newMutexRate)) + response.MutexRate = &oldMutexRate + } + + if opts.BlockRate != nil { + newBlockingRate := int(*opts.BlockRate) + runtime.SetBlockProfileRate(newBlockingRate) + + oldBlockingRate := uint64(savedBlockingRate.Load()) + response.BlockRate = &oldBlockingRate + savedBlockingRate.Store(int32(newBlockingRate)) + } + + return ctx.JSON(http.StatusOK, response) +} diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 53ef65256c..1952c88e7a 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -2439,3 +2439,66 @@ func TestGeneratePartkeys(t *testing.T) { } } + +func TestDebugExtraPprofEndpoint(t *testing.T) { + partitiontest.PartitionTest(t) + + submit := func(t *testing.T, method string, body []byte, expectedCode int) []byte { + handler := v2.Handlers{ + Node: nil, + Log: logging.Base(), + } + e := echo.New() + + var bodyReader io.Reader + if body != nil { + bodyReader = bytes.NewReader(body) + } + + req := httptest.NewRequest(method, "/debug/extra/pprof", bodyReader) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + if method == http.MethodPut { + handler.PutDebugSettingsProf(c) + } else { + handler.GetDebugSettingsProf(c) + } + require.Equal(t, expectedCode, rec.Code) + + return rec.Body.Bytes() + } + + // check original values + body := submit(t, http.MethodGet, nil, http.StatusOK) + require.Contains(t, string(body), `"mutex-rate":0`) + require.Contains(t, string(body), `"block-rate":0`) + + // enable mutex and blocking profiling, should return the original zero values + body = submit(t, http.MethodPut, []byte(`{"mutex-rate":1000, "block-rate":2000}`), http.StatusOK) + require.Contains(t, string(body), `"mutex-rate":0`) + require.Contains(t, string(body), `"block-rate":0`) + + // check the new values + body = submit(t, http.MethodGet, nil, http.StatusOK) + require.Contains(t, string(body), `"mutex-rate":1000`) + require.Contains(t, string(body), `"block-rate":2000`) + + // set invalid values + body = submit(t, http.MethodPut, []byte(`{"mutex-rate":-1, "block-rate":2000}`), http.StatusBadRequest) + require.Contains(t, string(body), "failed to decode object") + + body = submit(t, http.MethodPut, []byte(`{"mutex-rate":1000, "block-rate":-2}`), http.StatusBadRequest) + require.Contains(t, string(body), "failed to decode object") + + // disable mutex and blocking profiling + body = submit(t, http.MethodPut, []byte(`{"mutex-rate":0, "block-rate":0}`), http.StatusOK) + require.Contains(t, string(body), `"mutex-rate":1000`) + require.Contains(t, string(body), `"block-rate":2000`) + + // check it is disabled + body = submit(t, http.MethodGet, nil, http.StatusOK) + require.Contains(t, string(body), `"mutex-rate":0`) + require.Contains(t, string(body), `"block-rate":0`) + +} From 88b0ca5ecd6122db90cc4d73207dd1d39df31dc8 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:56:48 -0400 Subject: [PATCH 144/218] tests: fix TestVotersReloadFromDiskAfterOneStateProofCommitted (#6024) --- ledger/ledger_test.go | 32 +++++++++++++++++++++++++------- ledger/tracker.go | 3 +++ ledger/tracker_test.go | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 127ecf85cb..2a9666688b 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -2931,14 +2931,31 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi require.NoError(t, err) } - triggerDeleteVoters(t, l, genesisInitState) - l.acctsOnline.voters.votersMu.Lock() - vtSnapshot := l.acctsOnline.voters.votersForRoundCache + // wait all pending commits to finish + l.trackers.accountsWriting.Wait() - // verifying that the tree for round 512 is still in the cache, but the tree for round 256 is evicted. - require.Contains(t, vtSnapshot, basics.Round(496)) - require.NotContains(t, vtSnapshot, basics.Round(240)) - l.acctsOnline.voters.votersMu.Unlock() + // quit the commitSyncer goroutine + l.trackers.ctxCancel() + l.trackers.ctxCancel = nil + <-l.trackers.commitSyncerClosed + l.trackers.commitSyncerClosed = nil + + // flush one final time + triggerTrackerFlush(t, l) + + var vtSnapshot map[basics.Round]*ledgercore.VotersForRound + func() { + // grab internal lock in order to access the voters tracker + // since the assert below might fail, use a nested scope to ensure the lock is released + l.acctsOnline.voters.votersMu.Lock() + defer l.acctsOnline.voters.votersMu.Unlock() + + vtSnapshot = l.acctsOnline.voters.votersForRoundCache + + // verifying that the tree for round 512 is still in the cache, but the tree for round 256 is evicted. + require.Contains(t, vtSnapshot, basics.Round(496)) + require.NotContains(t, vtSnapshot, basics.Round(240)) + }() err = l.reloadLedger() require.NoError(t, err) @@ -2953,6 +2970,7 @@ func TestVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T) { cfg := config.GetDefaultLocal() cfg.Archival = false cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + cfg.CatchpointInterval = 0 // no need catchpoint for this test ledgertesting.WithAndWithoutLRUCache(t, cfg, testVotersReloadFromDiskAfterOneStateProofCommitted) } diff --git a/ledger/tracker.go b/ledger/tracker.go index d99f997ebe..97098a572f 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -547,6 +547,8 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { offset := dcc.offset dbRound := dcc.oldBase + tr.log.Debugf("commitRound called for (%d-%d)", dbRound, dbRound+basics.Round(offset)) + // we can exit right away, as this is the result of mis-ordered call to committedUpTo. if tr.dbRound < dbRound || offset < uint64(tr.dbRound-dbRound) { tr.log.Warnf("out of order deferred commit: offset %d, dbRound %d but current tracker DB round is %d", offset, dbRound, tr.dbRound) @@ -574,6 +576,7 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { dcc.offset = offset dcc.oldBase = dbRound dcc.flushTime = time.Now() + tr.log.Debugf("commitRound advancing tracker db snapshot (%d-%d)", dbRound, dbRound+basics.Round(offset)) var err error for _, lt := range tr.trackers { diff --git a/ledger/tracker_test.go b/ledger/tracker_test.go index 1c8a99ff41..9c26223c39 100644 --- a/ledger/tracker_test.go +++ b/ledger/tracker_test.go @@ -259,7 +259,7 @@ func (st *commitRoundStallingTracker) commitRound(context.Context, trackerdb.Tra // 3. Set a block in prepareCommit, and initiate the commit // 4. Set a block in produceCommittingTask, add a new block and resume the commit // 5. Resume produceCommittingTask -// 6. The data race and panic happens in block queue syncher thread +// 6. The data race and panic happens in block queue syncer thread func TestTrackers_DbRoundDataRace(t *testing.T) { partitiontest.PartitionTest(t) From 985512b7138fb0223d593cd4dd5565fb3bcf6144 Mon Sep 17 00:00:00 2001 From: Henrik Soerensen Date: Tue, 11 Jun 2024 13:49:59 -0400 Subject: [PATCH 145/218] network: Allow short-lived connections to query /status endpoint when at full capacity (#6009) Co-authored-by: ohill <145173879+ohill@users.noreply.github.com> --- config/localTemplate.go | 2 +- daemon/algod/server.go | 3 ++- network/requestTracker.go | 17 +++++++++++++++-- network/wsNetwork.go | 10 +++++++++- rpcs/healthService.go | 8 +++----- rpcs/healthService_test.go | 9 +++++---- 6 files changed, 35 insertions(+), 14 deletions(-) diff --git a/config/localTemplate.go b/config/localTemplate.go index 67218f8ade..afd97d2b73 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -130,7 +130,7 @@ type Local struct { LogArchiveDir string `version[31]:""` // IncomingConnectionsLimit specifies the max number of incoming connections - // for the port configured in NetAddress. 0 means no connections allowed. Must be non-negative. + // for the gossip protocol configured in NetAddress. 0 means no connections allowed. Must be non-negative. // Estimating 1.5MB per incoming connection, 1.5MB*2400 = 3.6GB IncomingConnectionsLimit int `version[0]:"-1" version[1]:"10000" version[17]:"800" version[27]:"2400"` diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 57e1b443f5..bf33def658 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -40,6 +40,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/network/limitlistener" "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/util" @@ -146,7 +147,7 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes if cfg.IsGossipServer() { var ot basics.OverflowTracker - fdRequired = ot.Add(fdRequired, uint64(cfg.IncomingConnectionsLimit)) + fdRequired = ot.Add(fdRequired, uint64(cfg.IncomingConnectionsLimit)+network.ReservedHealthServiceConnections) if ot.Overflowed { return errors.New("Initialize() overflowed when adding up IncomingConnectionsLimit to the existing RLIMIT_NOFILE value; decrease RestConnectionsHardLimit or IncomingConnectionsLimit") } diff --git a/network/requestTracker.go b/network/requestTracker.go index c88d4e5cf0..c38cc9d2ed 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -415,7 +415,7 @@ func (rt *RequestTracker) sendBlockedConnectionResponse(conn net.Conn, requestTi } } -// pruneAcceptedConnections clean stale items form the acceptedConnections map; it's syncornized via the acceptedConnectionsMu mutex which is expected to be taken by the caller. +// pruneAcceptedConnections clean stale items form the acceptedConnections map; it's syncornized via the hostRequestsMu mutex which is expected to be taken by the caller. // in case the created is 0, the pruning is disabled for this connection. The HTTP handlers would call Close to have this entry cleared out. func (rt *RequestTracker) pruneAcceptedConnections(pruneStartDate time.Time) { localAddrToRemove := []net.Addr{} @@ -494,6 +494,20 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http. localAddr := request.Context().Value(http.LocalAddrContextKey).(net.Addr) rt.hostRequestsMu.Lock() + // Check if the number of connections exceeds the limit + acceptedConnections := len(rt.acceptedConnections) + + if acceptedConnections > rt.config.IncomingConnectionsLimit && request.URL.Path != HealthServiceStatusPath { + rt.hostRequestsMu.Unlock() + // If the limit is exceeded, reject the connection + networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "rt_incoming_connection_limit"}) + rt.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerFailEvent, + telemetryspec.ConnectPeerFailEventDetails{ + Address: localAddr.String(), Incoming: true, Reason: "RequestTracker Connection Limit"}) + response.WriteHeader(http.StatusServiceUnavailable) + return + } + trackedRequest := rt.acceptedConnections[localAddr] if trackedRequest != nil { // update the original tracker request so that it won't get pruned. @@ -550,7 +564,6 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http. // send the request downstream; in our case, it would go to the router. rt.downstreamHandler.ServeHTTP(response, request) - } // remoteHostProxyFix updates the origin IP address in the trackedRequest diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 9c203d8839..a35375f517 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -107,6 +107,11 @@ const testingPublicAddress = "testing" // Maximum number of bytes to read from a header when trying to establish a websocket connection. const wsMaxHeaderBytes = 4096 +// ReservedHealthServiceConnections reserves additional connections for the health check endpoint. This reserves +// capacity to query the health check service when a node is serving maximum peers. The file descriptors will be +// used from the ReservedFDs pool, as this pool is meant for short-lived usage (dns queries, disk i/o, etc.) +const ReservedHealthServiceConnections = 10 + var networkIncomingConnections = metrics.MakeGauge(metrics.NetworkIncomingConnections) var networkOutgoingConnections = metrics.MakeGauge(metrics.NetworkOutgoingConnections) @@ -151,6 +156,9 @@ const peerShutdownDisconnectionAckDuration = 50 * time.Millisecond // Contains {genesisID} param to be handled by gorilla/mux const GossipNetworkPath = "/v1/{genesisID}/gossip" +// HealthServiceStatusPath is the path to register HealthService as a handler for when using gorilla/mux +const HealthServiceStatusPath = "/status" + // NodeInfo helps the network get information about the node it is running on type NodeInfo interface { // IsParticipating returns true if this node has stake and may vote on blocks or propose blocks. @@ -684,7 +692,7 @@ func (wn *WebsocketNetwork) Start() { } // wrap the original listener with a limited connection listener listener = limitlistener.RejectingLimitListener( - listener, uint64(wn.config.IncomingConnectionsLimit), wn.log) + listener, uint64(wn.config.IncomingConnectionsLimit)+ReservedHealthServiceConnections, wn.log) // wrap the limited connection listener with a requests tracker listener wn.listener = wn.requestsTracker.Listener(listener) wn.log.Debugf("listening on %s", wn.listener.Addr().String()) diff --git a/rpcs/healthService.go b/rpcs/healthService.go index d3121d8bd2..1c852c4e8e 100644 --- a/rpcs/healthService.go +++ b/rpcs/healthService.go @@ -17,12 +17,10 @@ package rpcs import ( - "github.com/algorand/go-algorand/network" "net/http" -) -// HealthServiceStatusPath is the path to register HealthService as a handler for when using gorilla/mux -const HealthServiceStatusPath = "/status" + "github.com/algorand/go-algorand/network" +) // HealthService is a service that provides health information endpoints for the node type HealthService struct{} @@ -31,7 +29,7 @@ type HealthService struct{} func MakeHealthService(net network.GossipNode) HealthService { service := HealthService{} - net.RegisterHTTPHandler(HealthServiceStatusPath, service) + net.RegisterHTTPHandler(network.HealthServiceStatusPath, service) return service } diff --git a/rpcs/healthService_test.go b/rpcs/healthService_test.go index 9d0bb215c2..f2846c322d 100644 --- a/rpcs/healthService_test.go +++ b/rpcs/healthService_test.go @@ -17,13 +17,14 @@ package rpcs import ( - "github.com/algorand/go-algorand/network" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" "io" "net/http" "path" "testing" + + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" ) func TestHealthService_ServeHTTP(t *testing.T) { @@ -40,7 +41,7 @@ func TestHealthService_ServeHTTP(t *testing.T) { client := http.Client{} - parsedURL.Path = path.Join(parsedURL.Path, HealthServiceStatusPath) + parsedURL.Path = path.Join(parsedURL.Path, network.HealthServiceStatusPath) response, err := client.Get(parsedURL.String()) require.NoError(t, err) From a5aac4228c13e43c118c99e94062bf23d1fb9846 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:22:26 -0400 Subject: [PATCH 146/218] tests: debug output on LibGoalFixture failure (#6026) --- test/framework/fixtures/libgoalFixture.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index f1a13111a0..d5140199da 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -144,20 +144,23 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e return } - defer func() { + debugLog := func() { f.t.Logf("Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err) f.dumpLogs(filepath.Join(nc.GetDataDir(), "node.log")) - }() + } exitError, ok := err.(*exec.ExitError) if !ok { - require.NoError(f.t, err, "Node at %s has terminated with an error", nc.GetDataDir()) + debugLog() + require.NoError(f.t, err) return } ws := exitError.Sys().(syscall.WaitStatus) exitCode := ws.ExitStatus() - require.NoError(f.t, err, "Node at %s has terminated with error code %d", nc.GetDataDir(), exitCode) + f.t.Logf("Node at %s has terminated with error code %d (%v)", nc.GetDataDir(), exitCode, *exitError) + debugLog() + require.NoError(f.t, err) } func (f *LibGoalFixture) importRootKeys(lg *libgoal.Client, dataDir string) { From 4caf2e4ccf987ebc1441ca72772efd50ada9d950 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 11 Jun 2024 17:56:04 -0400 Subject: [PATCH 147/218] AVM: Expose relevant incentive constants (#6025) --- data/transactions/logic/assembler_test.go | 30 ++++++++--------------- data/transactions/logic/eval.go | 10 ++++++++ data/transactions/logic/eval_test.go | 16 ++++++++++-- data/transactions/logic/fields.go | 26 ++++++++++++++++++++ data/transactions/logic/fields_string.go | 11 ++++++--- 5 files changed, 68 insertions(+), 25 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 79b37186df..32101dbcbc 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -633,28 +633,13 @@ func assembleWithTrace(text string, ver uint64) (*OpStream, error) { return &ops, err } -func lines(s string, num int) (bool, string) { - if num < 1 { - return true, "" - } - found := 0 - for i := 0; i < len(s); i++ { - if s[i] == '\n' { - found++ - if found == num { - return true, s[0 : i+1] - } - } - } - return false, s -} - func summarize(trace *strings.Builder) string { - truncated, msg := lines(trace.String(), 50) - if !truncated { - return msg + all := trace.String() + if strings.Count(all, "\n") < 50 { + return all } - return msg + "(trace truncated)\n" + lines := strings.Split(all, "\n") + return strings.Join(lines[:20], "\n") + "\n(some trace elided)\n" + strings.Join(lines[len(lines)-20:], "\n") } func testProg(t testing.TB, source string, ver uint64, expected ...expect) *OpStream { @@ -1720,6 +1705,11 @@ pushint 1 block BlkFeesCollected pushint 1 block BlkBonus +global PayoutsEnabled +global PayoutsGoOnlineFee +global PayoutsPercent +global PayoutsMinBalance +global PayoutsMaxBalance `, AssemblerMaxVersion) for _, names := range [][]string{GlobalFieldNames[:], TxnFieldNames[:], blockFieldNames[:]} { for _, f := range names { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 36ba3bb788..3201877ed9 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -3737,6 +3737,16 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er case GenesisHash: gh := cx.SigLedger.GenesisHash() sv.Bytes = gh[:] + case PayoutsEnabled: + sv.Uint = boolToUint(cx.Proto.Payouts.Enabled) + case PayoutsGoOnlineFee: + sv.Uint = cx.Proto.Payouts.GoOnlineFee + case PayoutsPercent: + sv.Uint = cx.Proto.Payouts.Percent + case PayoutsMinBalance: + sv.Uint = cx.Proto.Payouts.MinBalance + case PayoutsMaxBalance: + sv.Uint = cx.Proto.Payouts.MaxBalance default: return sv, fmt.Errorf("invalid global field %s", fs.field) } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 3a81a9cbef..c8f7a8bc5f 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -125,6 +125,14 @@ func makeTestProto(opts ...protoOpt) *config.ConsensusParams { MaxBoxSize: 1000, BytesPerBoxReference: 100, + + Payouts: config.ProposerPayoutRules{ + Enabled: true, + GoOnlineFee: 3, + Percent: 4, + MinBalance: 5, + MaxBalance: 6, + }, } for _, opt := range opts { if opt != nil { // so some callsites can take one arg and pass it in @@ -1234,7 +1242,11 @@ global GenesisHash; len; int 32; ==; && ` const globalV11TestProgram = globalV10TestProgram + ` -// No new globals in v11 +global PayoutsEnabled; assert +global PayoutsGoOnlineFee; int 3; ==; assert +global PayoutsPercent; int 4; ==; assert +global PayoutsMinBalance; int 5; ==; assert +global PayoutsMaxBalance; int 6; ==; assert ` func TestAllGlobals(t *testing.T) { @@ -1258,7 +1270,7 @@ func TestAllGlobals(t *testing.T) { 8: {CallerApplicationAddress, globalV8TestProgram}, 9: {CallerApplicationAddress, globalV9TestProgram}, 10: {GenesisHash, globalV10TestProgram}, - 11: {GenesisHash, globalV11TestProgram}, + 11: {PayoutsMaxBalance, globalV11TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 367967089b..99cc08bad2 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -538,6 +538,21 @@ const ( // GenesisHash is the genesis hash for the network GenesisHash + // PayoutsEnabled is whether block proposal payouts are enabled + PayoutsEnabled + + // PayoutsGoOnlineFee is the fee required in a keyreg transaction to make an account incentive eligible + PayoutsGoOnlineFee + + // PayoutsPercent is the percentage of transaction fees in a block that can be paid to the block proposer. + PayoutsPercent + + // PayoutsMinBalance is the minimum algo balance an account must have to receive block payouts (in the agreement round). + PayoutsMinBalance + + // PayoutsMaxBalance is the maximum algo balance an account can have to receive block payouts (in the agreement round). + PayoutsMaxBalance + invalidGlobalField // compile-time constant for number of fields ) @@ -603,6 +618,17 @@ var globalFieldSpecs = [...]globalFieldSpec{ {AssetOptInMinBalance, StackUint64, modeAny, 10, "The additional minimum balance required to opt-in to an asset."}, {GenesisHash, StackBytes32, modeAny, 10, "The Genesis Hash for the network."}, + + {PayoutsEnabled, StackBoolean, modeAny, incentiveVersion, + "Whether block proposal payouts are enabled."}, + {PayoutsGoOnlineFee, StackUint64, modeAny, incentiveVersion, + "The fee required in a keyreg transaction to make an account incentive eligible."}, + {PayoutsPercent, StackUint64, modeAny, incentiveVersion, + "The percentage of transaction fees in a block that can be paid to the block proposer."}, + {PayoutsMinBalance, StackUint64, modeAny, incentiveVersion, + "The minimum algo balance an account must have in the agreement round to receive block payouts in the proposal round."}, + {PayoutsMaxBalance, StackUint64, modeAny, incentiveVersion, + "The maximum algo balance an account can have in the agreement round to receive block payouts in the proposal round."}, } func globalFieldSpecByField(f GlobalField) (globalFieldSpec, bool) { diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index d925e69ee1..3463da269c 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -111,12 +111,17 @@ func _() { _ = x[AssetCreateMinBalance-15] _ = x[AssetOptInMinBalance-16] _ = x[GenesisHash-17] - _ = x[invalidGlobalField-18] + _ = x[PayoutsEnabled-18] + _ = x[PayoutsGoOnlineFee-19] + _ = x[PayoutsPercent-20] + _ = x[PayoutsMinBalance-21] + _ = x[PayoutsMaxBalance-22] + _ = x[invalidGlobalField-23] } -const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDOpcodeBudgetCallerApplicationIDCallerApplicationAddressAssetCreateMinBalanceAssetOptInMinBalanceGenesisHashinvalidGlobalField" +const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDOpcodeBudgetCallerApplicationIDCallerApplicationAddressAssetCreateMinBalanceAssetOptInMinBalanceGenesisHashPayoutsEnabledPayoutsGoOnlineFeePayoutsPercentPayoutsMinBalancePayoutsMaxBalanceinvalidGlobalField" -var _GlobalField_index = [...]uint16{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 162, 181, 205, 226, 246, 257, 275} +var _GlobalField_index = [...]uint16{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 162, 181, 205, 226, 246, 257, 271, 289, 303, 320, 337, 355} func (i GlobalField) String() string { if i >= GlobalField(len(_GlobalField_index)-1) { From 7f8939941ae5882b80e21b49be471a2f89398dca Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:46:14 -0400 Subject: [PATCH 148/218] libgoal: output debug info on failure to stderr (#6027) --- test/framework/fixtures/libgoalFixture.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index d5140199da..40269aa111 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -145,7 +145,7 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e } debugLog := func() { - f.t.Logf("Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err) + fmt.Fprintf(os.Stderr, "Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err) f.dumpLogs(filepath.Join(nc.GetDataDir(), "node.log")) } @@ -158,7 +158,7 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e ws := exitError.Sys().(syscall.WaitStatus) exitCode := ws.ExitStatus() - f.t.Logf("Node at %s has terminated with error code %d (%v)", nc.GetDataDir(), exitCode, *exitError) + fmt.Fprintf(os.Stderr, "Node at %s has terminated with error code %d (%v)", nc.GetDataDir(), exitCode, *exitError) debugLog() require.NoError(f.t, err) } @@ -369,18 +369,19 @@ func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { func (f *LibGoalFixture) dumpLogs(filePath string) { file, err := os.Open(filePath) if err != nil { - f.t.Logf("could not open %s", filePath) + fmt.Fprintf(os.Stderr, "could not open %s", filePath) return } defer file.Close() - f.t.Log("=================================\n") + fmt.Fprintf(os.Stderr, "=================================\n") parts := strings.Split(filePath, "/") - f.t.Logf("%s/%s:", parts[len(parts)-2], parts[len(parts)-1]) // Primary/node.log + fmt.Fprintf(os.Stderr, "%s/%s:", parts[len(parts)-2], parts[len(parts)-1]) // Primary/node.log scanner := bufio.NewScanner(file) for scanner.Scan() { - f.t.Logf(scanner.Text()) + fmt.Fprint(os.Stderr, scanner.Text()) } + fmt.Fprintln(os.Stderr) } // intercept baseFixture.failOnError so we can clean up any algods that are still alive From 9191b1befc7cc9a30402d128f562369baa60d066 Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:08:09 -0400 Subject: [PATCH 149/218] config: Add GoMemLimit config option and use with 10-node test (#5975) --- cmd/algod/main.go | 6 + config/localTemplate.go | 6 +- config/local_defaults.go | 3 +- installer/config.json.example | 3 +- .../catchup/stateproofsCatchup_test.go | 6 + .../partitionRecovery_test.go | 1 + test/testdata/configs/config-v34.json | 139 ++++++++++++++++++ 7 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 test/testdata/configs/config-v34.json diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 603f543b89..0f93ed447f 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -22,6 +22,7 @@ import ( "math/rand" "os" "path/filepath" + "runtime/debug" "strconv" "strings" "time" @@ -173,6 +174,11 @@ func run() int { log.Fatalf("Cannot load config: %v", err) } + // set soft memory limit, if configured + if cfg.GoMemLimit > 0 { + debug.SetMemoryLimit(int64(cfg.GoMemLimit)) + } + _, err = cfg.ValidateDNSBootstrapArray(genesis.Network) if err != nil { // log is not setup yet, this will log to stderr diff --git a/config/localTemplate.go b/config/localTemplate.go index afd97d2b73..309ffcb798 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -43,7 +43,7 @@ type Local struct { // Version tracks the current version of the defaults so we can migrate old -> new // This is specifically important whenever we decide to change the default value // for an existing parameter. This field tag must be updated any time we add a new version. - Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33"` + Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33" version[34]:"34"` // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only affects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. Archival bool `version[0]:"false"` @@ -610,6 +610,10 @@ type Local struct { // DisableAPIAuth turns off authentication for public (non-admin) API endpoints. DisableAPIAuth bool `version[30]:"false"` + + // GoMemLimit provides the Go runtime with a soft memory limit. The default behavior is no limit, + // unless the GOMEMLIMIT environment variable is set. + GoMemLimit uint64 `version[34]:"0"` } // DNSBootstrapArray returns an array of one or more DNS Bootstrap identifiers diff --git a/config/local_defaults.go b/config/local_defaults.go index d2a73d4c6f..f5f02082aa 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -20,7 +20,7 @@ package config var defaultLocal = Local{ - Version: 33, + Version: 34, AccountUpdatesStatsInterval: 5000000000, AccountsRebuildSynchronousMode: 1, AgreementIncomingBundlesQueueLength: 15, @@ -89,6 +89,7 @@ var defaultLocal = Local{ FallbackDNSResolverAddress: "", ForceFetchTransactions: false, ForceRelayMessages: false, + GoMemLimit: 0, GossipFanout: 4, HeartbeatUpdateInterval: 600, HotDataDir: "", diff --git a/installer/config.json.example b/installer/config.json.example index d9188ef748..4a9714115f 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -1,5 +1,5 @@ { - "Version": 33, + "Version": 34, "AccountUpdatesStatsInterval": 5000000000, "AccountsRebuildSynchronousMode": 1, "AgreementIncomingBundlesQueueLength": 15, @@ -68,6 +68,7 @@ "FallbackDNSResolverAddress": "", "ForceFetchTransactions": false, "ForceRelayMessages": false, + "GoMemLimit": 0, "GossipFanout": 4, "HeartbeatUpdateInterval": 600, "HotDataDir": "", diff --git a/test/e2e-go/features/catchup/stateproofsCatchup_test.go b/test/e2e-go/features/catchup/stateproofsCatchup_test.go index 9de6bf385c..5dcbc11452 100644 --- a/test/e2e-go/features/catchup/stateproofsCatchup_test.go +++ b/test/e2e-go/features/catchup/stateproofsCatchup_test.go @@ -221,6 +221,12 @@ func TestSendSigsAfterCatchpointCatchup(t *testing.T) { var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) fixture.SetupNoStart(t, filepath.Join("nettemplates", "ThreeNodesWithRichAcct.json")) + for _, nodeDir := range fixture.NodeDataDirs() { + cfg, err := config.LoadConfigFromDisk(nodeDir) + a.NoError(err) + cfg.GoMemLimit = 4 * 1024 * 1024 * 1024 // 4GB + cfg.SaveToDisk(nodeDir) + } primaryNode, primaryNodeRestClient, primaryEC := startCatchpointGeneratingNode(a, &fixture, "Primary") defer primaryEC.Print() diff --git a/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go b/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go index 284146864d..21ce3bdf0d 100644 --- a/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go +++ b/test/e2e-go/features/partitionRecovery/partitionRecovery_test.go @@ -251,6 +251,7 @@ func TestPartitionHalfOffline(t *testing.T) { a.NoError(err) // adjust the refresh interval for one hour, so that we won't be reloading the participation key during this test. cfg.ParticipationKeysRefreshInterval = time.Hour + cfg.GoMemLimit = 1 * 1024 * 1024 * 1024 // 1GB cfg.SaveToDisk(nodeDir) } fixture.Start() diff --git a/test/testdata/configs/config-v34.json b/test/testdata/configs/config-v34.json new file mode 100644 index 0000000000..4a9714115f --- /dev/null +++ b/test/testdata/configs/config-v34.json @@ -0,0 +1,139 @@ +{ + "Version": 34, + "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, + "EnableDeveloperAPI": false, + "EnableExperimentalAPI": false, + "EnableFollowMode": false, + "EnableGossipBlockService": true, + "EnableGossipService": 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, + "GoMemLimit": 0, + "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 +} From dbe60eeec8cab9a2e4cb29af3ab1290e8ad151be Mon Sep 17 00:00:00 2001 From: John Lee Date: Wed, 12 Jun 2024 13:08:40 -0400 Subject: [PATCH 150/218] Legacy Docker: pin ubuntu version (#6028) --- docker/releases/build_releases.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/releases/build_releases.sh b/docker/releases/build_releases.sh index 1862c7ae99..11376eba48 100755 --- a/docker/releases/build_releases.sh +++ b/docker/releases/build_releases.sh @@ -76,7 +76,7 @@ case $NETWORK in esac IFS='' read -r -d '' DOCKERFILE < Date: Wed, 12 Jun 2024 13:22:45 -0400 Subject: [PATCH 151/218] config: Add warning if EndpointAddress and NetAddress ports are equal (#6006) Co-authored-by: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> --- daemon/algod/server.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/daemon/algod/server.go b/daemon/algod/server.go index bf33def658..12cbf3e968 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -24,6 +24,7 @@ import ( "net" "net/http" _ "net/http/pprof" // net/http/pprof is for registering the pprof URLs with the web server, so http://localhost:8080/debug/pprof/ works. + "net/url" "os" "os/signal" "path/filepath" @@ -269,6 +270,19 @@ func makeListener(addr string) (net.Listener, error) { return net.Listen("tcp", addr) } +// helper to get port from an address +func getPortFromAddress(addr string) (string, error) { + u, err := url.Parse(addr) + if err == nil && u.Scheme != "" { + addr = u.Host + } + _, port, err := net.SplitHostPort(addr) + if err != nil { + return "", fmt.Errorf("Error parsing address: %v", err) + } + return port, nil +} + // Start starts a Node instance and its network services func (s *Server) Start() { s.log.Info("Trying to start an Algorand node") @@ -359,6 +373,20 @@ func (s *Server) Start() { fmt.Printf("netlistenfile error: %v\n", err) os.Exit(1) } + + addrPort, err := getPortFromAddress(addr) + if err != nil { + s.log.Warnf("Error getting port from EndpointAddress: %v", err) + } + + listenAddrPort, err := getPortFromAddress(listenAddr) + if err != nil { + s.log.Warnf("Error getting port from NetAddress: %v", err) + } + + if addrPort == listenAddrPort { + s.log.Warnf("EndpointAddress port %v matches NetAddress port %v. This may lead to unexpected results when accessing endpoints.", addrPort, listenAddrPort) + } } errChan := make(chan error, 1) From 95c9c1837cf466c2c9f423864c9901dc26e0e7c1 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:52:13 -0400 Subject: [PATCH 152/218] tests: preserve logs on LibGoalFixture failure (#6030) --- agreement/service.go | 3 ++ catchup/catchpointService.go | 3 ++ catchup/service.go | 3 ++ data/txHandler.go | 3 ++ netdeploy/network.go | 15 +++++--- network/wsNetwork.go | 3 ++ node/impls.go | 2 + node/node.go | 7 ++++ nodecontrol/algodControl.go | 9 +++++ rpcs/blockService.go | 3 ++ rpcs/ledgerService.go | 3 ++ rpcs/txSyncer.go | 3 ++ stateproof/worker.go | 3 ++ .../features/catchup/basicCatchup_test.go | 17 +++++++++ test/framework/fixtures/libgoalFixture.go | 37 ++++++++++++------- 15 files changed, 96 insertions(+), 18 deletions(-) diff --git a/agreement/service.go b/agreement/service.go index d8ec84b92f..8e38797492 100644 --- a/agreement/service.go +++ b/agreement/service.go @@ -173,6 +173,9 @@ func (s *Service) Start() { // // This method returns after all resources have been cleaned up. func (s *Service) Shutdown() { + s.log.Debug("agreement service is stopping") + defer s.log.Debug("agreement service has stopped") + close(s.quit) s.quitFn() <-s.done diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 974f5964f1..efa34436a3 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -185,6 +185,9 @@ func (cs *CatchpointCatchupService) Abort() { // Stop stops the catchpoint catchup service - unlike Abort, this is not intended to abort the process but rather to allow // cleanup of in-memory resources for the purpose of clean shutdown. func (cs *CatchpointCatchupService) Stop() { + cs.log.Debug("catchpoint service is stopping") + defer cs.log.Debug("catchpoint service has stopped") + // signal the running goroutine that we want to stop cs.cancelCtxFunc() // wait for the running goroutine to terminate. diff --git a/catchup/service.go b/catchup/service.go index 5c6609b236..b1720c4fce 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -161,6 +161,9 @@ func (s *Service) Start() { // Stop informs the catchup service that it should stop, and waits for it to stop (when periodicSync() exits) func (s *Service) Stop() { + s.log.Debug("catchup service is stopping") + defer s.log.Debug("catchup service has stopped") + s.cancel() s.workers.Wait() if s.initialSyncNotified.CompareAndSwap(0, 1) { diff --git a/data/txHandler.go b/data/txHandler.go index 7851889378..871e71cbc3 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -254,6 +254,9 @@ func (handler *TxHandler) Start() { // Stop suspends the processing of incoming messages at the transaction handler func (handler *TxHandler) Stop() { + logging.Base().Debug("transaction handler is stopping") + defer logging.Base().Debug("transaction handler is stopping") + handler.ctxCancel() if handler.erl != nil { handler.erl.Stop() diff --git a/netdeploy/network.go b/netdeploy/network.go index b26c8ef5bc..6f31673a54 100644 --- a/netdeploy/network.go +++ b/netdeploy/network.go @@ -415,13 +415,14 @@ func (n Network) StartNode(binDir, nodeDir string, redirectOutput bool) (err err // Stop the network, ensuring primary relay stops first // No return code - we try to kill them if we can (if we read valid PID file) -func (n Network) Stop(binDir string) { - c := make(chan struct{}, len(n.cfg.RelayDirs)+len(n.nodeDirs)) +func (n Network) Stop(binDir string) (err error) { + c := make(chan error, len(n.cfg.RelayDirs)+len(n.nodeDirs)) stopNodeContoller := func(nc *nodecontrol.NodeController) { + var stopErr error defer func() { - c <- struct{}{} + c <- stopErr }() - nc.FullStop() + stopErr = nc.FullStop() } for _, relayDir := range n.cfg.RelayDirs { relayDataDir := n.getNodeFullPath(relayDir) @@ -439,9 +440,13 @@ func (n Network) Stop(binDir string) { } // wait until we finish stopping all the node controllers. for i := cap(c); i > 0; i-- { - <-c + stopErr := <-c + if stopErr != nil { + err = stopErr + } } close(c) + return err } // NetworkNodeStatus represents the result from checking the status of a particular node instance diff --git a/network/wsNetwork.go b/network/wsNetwork.go index a35375f517..6fc97def5e 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -793,6 +793,9 @@ func (wn *WebsocketNetwork) innerStop() { // Stop closes network connections and stops threads. // Stop blocks until all activity on this node is done. func (wn *WebsocketNetwork) Stop() { + wn.log.Debug("network is stopping") + defer wn.log.Debug("network has stopped") + wn.handler.ClearHandlers([]Tag{}) // if we have a working ticker, just stop it and clear it out. The access to this variable is safe since the Start()/Stop() are synced by the diff --git a/node/impls.go b/node/impls.go index 826f0399c4..3063e09659 100644 --- a/node/impls.go +++ b/node/impls.go @@ -44,6 +44,8 @@ func (i blockAuthenticatorImpl) Authenticate(block *bookkeeping.Block, cert *agr } func (i blockAuthenticatorImpl) Quit() { + logging.Base().Debug("block authenticator is stopping") + defer logging.Base().Debug("block authenticator has stopped") i.AsyncVoteVerifier.Quit() } diff --git a/node/node.go b/node/node.go index acf204facf..d1c6cc4b82 100644 --- a/node/node.go +++ b/node/node.go @@ -396,6 +396,8 @@ func (node *AlgorandFullNode) startMonitoringRoutines() { // waitMonitoringRoutines waits for all the monitoring routines to exit. Note that // the node.mu must not be taken, and that the node's context should have been canceled. func (node *AlgorandFullNode) waitMonitoringRoutines() { + node.log.Debug("waiting on node monitoring routines to exit") + defer node.log.Debug("done waiting on node monitoring routines to exit") node.monitoringRoutinesWaitGroup.Wait() } @@ -409,6 +411,9 @@ func (node *AlgorandFullNode) ListeningAddress() (string, bool) { // Stop stops running the node. Once a node is closed, it can never start again. func (node *AlgorandFullNode) Stop() { + node.log.Debug("algorand node is stopping") + defer node.log.Debug("algorand node has stopped") + node.mu.Lock() defer func() { node.mu.Unlock() @@ -431,9 +436,11 @@ func (node *AlgorandFullNode) Stop() { node.ledgerService.Stop() } node.catchupBlockAuth.Quit() + node.log.Debug("crypto worker pools are stopping") node.highPriorityCryptoVerificationPool.Shutdown() node.lowPriorityCryptoVerificationPool.Shutdown() node.cryptoPool.Shutdown() + node.log.Debug("crypto worker pools have stopped") node.cancelCtx() } diff --git a/nodecontrol/algodControl.go b/nodecontrol/algodControl.go index 74137a72eb..a6bcb5fb82 100644 --- a/nodecontrol/algodControl.go +++ b/nodecontrol/algodControl.go @@ -49,6 +49,14 @@ func (e *NodeNotRunningError) Error() string { return fmt.Sprintf("no running node in directory '%s'", e.algodDataDir) } +// NodeKilledError thrown when StopAlgod is called but the node was killed by SIGKILL instead of a clean shutdown with SIGTERM +type NodeKilledError struct { +} + +func (e *NodeKilledError) Error() string { + return "node was killed" +} + // MissingDataDirError thrown when StopAlgod is called but requested directory does not exist type MissingDataDirError struct { algodDataDir string @@ -176,6 +184,7 @@ func (nc *NodeController) StopAlgod() (err error) { if killed { // delete the pid file. os.Remove(nc.algodPidFile) + return &NodeKilledError{} } } else { return &NodeNotRunningError{algodDataDir: nc.algodDataDir} diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 8231b5a98b..d1ef82dfd4 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -169,6 +169,9 @@ func (bs *BlockService) Start() { // Stop servicing catchup requests over ws func (bs *BlockService) Stop() { + bs.log.Debug("block service is stopping") + defer bs.log.Debug("block service has stopped") + bs.mu.Lock() close(bs.stop) bs.mu.Unlock() diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go index b3742bb985..5f75a36b0c 100644 --- a/rpcs/ledgerService.go +++ b/rpcs/ledgerService.go @@ -96,6 +96,9 @@ func (ls *LedgerService) Start() { // Stop servicing catchup requests func (ls *LedgerService) Stop() { if ls.enableService { + logging.Base().Debug("ledger service is stopping") + defer logging.Base().Debug("ledger service has stopped") + ls.running.Store(0) ls.stopping.Wait() } diff --git a/rpcs/txSyncer.go b/rpcs/txSyncer.go index 1d7fc2c027..2d2a993d9a 100644 --- a/rpcs/txSyncer.go +++ b/rpcs/txSyncer.go @@ -97,6 +97,9 @@ func (syncer *TxSyncer) Start(canStart chan struct{}) { // Stop stops periodic syncing func (syncer *TxSyncer) Stop() { + syncer.log.Debug("transaction syncer is stopping") + defer syncer.log.Debug("transaction syncer has stopped") + syncer.cancel() syncer.wg.Wait() } diff --git a/stateproof/worker.go b/stateproof/worker.go index e73a06d137..f74e118f58 100644 --- a/stateproof/worker.go +++ b/stateproof/worker.go @@ -142,6 +142,9 @@ func (spw *Worker) initDb(inMemory bool) error { // Stop stops any goroutines associated with this worker. It is the caller responsibility to remove the register // network handlers func (spw *Worker) Stop() { + spw.log.Debug("stateproof worker is stopping") + defer spw.log.Debug("stateproof worker has stopped") + spw.shutdown() spw.wg.Wait() diff --git a/test/e2e-go/features/catchup/basicCatchup_test.go b/test/e2e-go/features/catchup/basicCatchup_test.go index 2e3ac87943..938313206d 100644 --- a/test/e2e-go/features/catchup/basicCatchup_test.go +++ b/test/e2e-go/features/catchup/basicCatchup_test.go @@ -128,6 +128,15 @@ func runCatchupOverGossip(t fixtures.TestingTB, a.NoError(err) a.Empty(cfg.NetworkProtocolVersion) cfg.NetworkProtocolVersion = ledgerNodeDowngradeTo + cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test + cfg.SaveToDisk(dir) + } else { + // TODO: remove when TestCatchupOverGossip is fixed + dir, err := fixture.GetNodeDir("Node") + a.NoError(err) + cfg, err := config.LoadConfigFromDisk(dir) + a.NoError(err) + cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test cfg.SaveToDisk(dir) } @@ -138,6 +147,14 @@ func runCatchupOverGossip(t fixtures.TestingTB, a.NoError(err) a.Empty(cfg.NetworkProtocolVersion) cfg.NetworkProtocolVersion = fetcherNodeDowngradeTo + cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test + cfg.SaveToDisk(dir) + } else { + // TODO: remove when TestCatchupOverGossip is fixed + dir := fixture.PrimaryDataDir() + cfg, err := config.LoadConfigFromDisk(dir) + a.NoError(err) + cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test cfg.SaveToDisk(dir) } diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 40269aa111..de1a06623d 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -145,7 +145,7 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e } debugLog := func() { - fmt.Fprintf(os.Stderr, "Node at %s has terminated with an error: %v. Dumping logs...", nc.GetDataDir(), err) + fmt.Fprintf(os.Stderr, "Node at %s has terminated with an error: %v. Dumping logs...\n", nc.GetDataDir(), err) f.dumpLogs(filepath.Join(nc.GetDataDir(), "node.log")) } @@ -158,7 +158,7 @@ func (f *LibGoalFixture) nodeExitWithError(nc *nodecontrol.NodeController, err e ws := exitError.Sys().(syscall.WaitStatus) exitCode := ws.ExitStatus() - fmt.Fprintf(os.Stderr, "Node at %s has terminated with error code %d (%v)", nc.GetDataDir(), exitCode, *exitError) + fmt.Fprintf(os.Stderr, "Node at %s has terminated with error code %d (%v)\n", nc.GetDataDir(), exitCode, *exitError) debugLog() require.NoError(f.t, err) } @@ -345,7 +345,10 @@ func (f *LibGoalFixture) Shutdown() { func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { f.NC.StopKMD() if preserveData { - f.network.Stop(f.binDir) + err := f.network.Stop(f.binDir) + if err != nil { + f.t.Logf("Fixture %s shutdown caught a network stop error: %v", f.Name, err) + } for _, relayDir := range f.RelayDataDirs() { f.dumpLogs(filepath.Join(relayDir, "node.log")) } @@ -353,14 +356,22 @@ func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { f.dumpLogs(filepath.Join(nodeDir, "node.log")) } } else { - f.network.Delete(f.binDir) - - // Remove the test dir, if it was created by us as a temporary - // directory and it is empty. If there's anything still in the - // test dir, os.Remove()'s rmdir will fail and have no effect; - // we ignore this error. - if f.testDirTmp { - os.Remove(f.testDir) + err := f.network.Stop(f.binDir) + if err == nil { + // no error, proceed with cleanup + delErr := f.network.Delete(f.binDir) + if delErr != nil { + f.t.Logf("Fixture %s shutdown caught a network delete error: %v", f.Name, delErr) + } + // Remove the test dir, if it was created by us as a temporary + // directory and it is empty. If there's anything still in the + // test dir, os.Remove()'s rmdir will fail and have no effect; + // we ignore this error. + if f.testDirTmp { + os.Remove(f.testDir) + } + } else { + f.t.Logf("Fixture %s shutdown caught a network stop error: %v", f.Name, err) } } } @@ -369,14 +380,14 @@ func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { func (f *LibGoalFixture) dumpLogs(filePath string) { file, err := os.Open(filePath) if err != nil { - fmt.Fprintf(os.Stderr, "could not open %s", filePath) + fmt.Fprintf(os.Stderr, "could not open %s\n", filePath) return } defer file.Close() fmt.Fprintf(os.Stderr, "=================================\n") parts := strings.Split(filePath, "/") - fmt.Fprintf(os.Stderr, "%s/%s:", parts[len(parts)-2], parts[len(parts)-1]) // Primary/node.log + fmt.Fprintf(os.Stderr, "%s/%s:\n", parts[len(parts)-2], parts[len(parts)-1]) // Primary/node.log scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Fprint(os.Stderr, scanner.Text()) From 97ab5593f86d26e09add68d55baefb4eeee4964a Mon Sep 17 00:00:00 2001 From: Joe Polny <50534337+joe-p@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:18:10 -0400 Subject: [PATCH 153/218] simulate: fix signers (#5942) Co-authored-by: Jason Paulos --- daemon/algod/api/algod.oas2.json | 13 + daemon/algod/api/algod.oas3.yml | 13 + .../api/server/v2/generated/data/routes.go | 428 ++++++------- .../v2/generated/experimental/routes.go | 449 ++++++------- .../api/server/v2/generated/model/types.go | 9 + .../nonparticipating/private/routes.go | 119 ++-- .../nonparticipating/public/routes.go | 602 +++++++++--------- .../generated/participating/private/routes.go | 126 ++-- .../generated/participating/public/routes.go | 415 ++++++------ daemon/algod/api/server/v2/handlers.go | 2 + daemon/algod/api/server/v2/utils.go | 11 +- ledger/simulation/simulation_eval_test.go | 448 +++++++++++++ ledger/simulation/simulator.go | 98 ++- ledger/simulation/simulator_test.go | 2 +- ledger/simulation/trace.go | 6 + ledger/simulation/tracer.go | 46 +- .../restAPI/simulate/simulateRestAPI_test.go | 83 +++ 17 files changed, 1788 insertions(+), 1082 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 36e4f34861..e0827feb16 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -3944,6 +3944,10 @@ }, "exec-trace-config": { "$ref": "#/definitions/SimulateTraceConfig" + }, + "fix-signers": { + "description": "If true, signers for transactions that are missing signatures will be fixed during evaluation.", + "type": "boolean" } } }, @@ -4287,6 +4291,11 @@ }, "unnamed-resources-accessed": { "$ref": "#/definitions/SimulateUnnamedResourcesAccessed" + }, + "fixed-signer":{ + "description": "The account that needed to sign this transaction when no signature was provided and the provided signer was incorrect.", + "type": "string", + "x-algorand-format": "Address" } } }, @@ -4393,6 +4402,10 @@ "extra-opcode-budget": { "description": "The extra opcode budget added to each transaction group during simulation", "type": "integer" + }, + "fix-signers": { + "description": "If true, signers for transactions that are missing signatures will be fixed during evaluation.", + "type": "boolean" } } }, diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 6737a95072..3fb04f5add 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -2302,6 +2302,10 @@ "description": "Applies extra opcode budget during simulation for each transaction group.", "type": "integer" }, + "fix-signers": { + "description": "If true, signers for transactions that are missing signatures will be fixed during evaluation.", + "type": "boolean" + }, "round": { "description": "If provided, specifies the round preceding the simulation. State changes through this round will be used to run this simulation. Usually only the 4 most recent rounds will be available (controlled by the node config value MaxAcctLookback). If not specified, defaults to the latest available round.", "type": "integer" @@ -2408,6 +2412,11 @@ "exec-trace": { "$ref": "#/components/schemas/SimulationTransactionExecTrace" }, + "fixed-signer": { + "description": "The account that needed to sign this transaction when no signature was provided and the provided signer was incorrect.", + "type": "string", + "x-algorand-format": "Address" + }, "logic-sig-budget-consumed": { "description": "Budget used during execution of a logic sig transaction.", "type": "integer" @@ -2494,6 +2503,10 @@ "description": "The extra opcode budget added to each transaction group during simulation", "type": "integer" }, + "fix-signers": { + "description": "If true, signers for transactions that are missing signatures will be fixed during evaluation.", + "type": "boolean" + }, "max-log-calls": { "description": "The maximum log calls one can make during simulation", "type": "integer" diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index 504ee50c69..dc0159d20d 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -114,225 +114,227 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "/xb9Ejq6CAEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index f78cbd49a6..5d9044ed56 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -130,230 +130,231 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "j08ezj7+/wAAAP//hDzDXLsHAQA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/model/types.go b/daemon/algod/api/server/v2/generated/model/types.go index e2d32257ba..d03f3d99f0 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -798,6 +798,9 @@ type SimulateRequest struct { // ExtraOpcodeBudget Applies extra opcode budget during simulation for each transaction group. ExtraOpcodeBudget *uint64 `json:"extra-opcode-budget,omitempty"` + // FixSigners If true, signers for transactions that are missing signatures will be fixed during evaluation. + FixSigners *bool `json:"fix-signers,omitempty"` + // Round If provided, specifies the round preceding the simulation. State changes through this round will be used to run this simulation. Usually only the 4 most recent rounds will be available (controlled by the node config value MaxAcctLookback). If not specified, defaults to the latest available round. Round *uint64 `json:"round,omitempty"` @@ -855,6 +858,9 @@ type SimulateTransactionResult struct { // ExecTrace The execution trace of calling an app or a logic sig, containing the inner app call trace in a recursive way. ExecTrace *SimulationTransactionExecTrace `json:"exec-trace,omitempty"` + // FixedSigner The account that needed to sign this transaction when no signature was provided and the provided signer was incorrect. + FixedSigner *string `json:"fixed-signer,omitempty"` + // LogicSigBudgetConsumed Budget used during execution of a logic sig transaction. LogicSigBudgetConsumed *uint64 `json:"logic-sig-budget-consumed,omitempty"` @@ -900,6 +906,9 @@ type SimulationEvalOverrides struct { // ExtraOpcodeBudget The extra opcode budget added to each transaction group during simulation ExtraOpcodeBudget *uint64 `json:"extra-opcode-budget,omitempty"` + // FixSigners If true, signers for transactions that are missing signatures will be fixed during evaluation. + FixSigners *bool `json:"fix-signers,omitempty"` + // MaxLogCalls The maximum log calls one can make during simulation MaxLogCalls *uint64 `json:"max-log-calls,omitempty"` diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index bf31180ecc..7e8da1ba91 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -334,65 +334,66 @@ var swaggerSpec = []string{ "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", - "h/8fAAD//3HzSP3DCwEA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 18ea670c4d..31d870e45e 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -746,306 +746,308 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "z0/tvgEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 24f66d0cde..981fddde61 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -398,68 +398,70 @@ var swaggerSpec = []string{ "78/czNLld0uloVOM3fWmTJ/NwxdMo4n/WQirud7dJEffoIT8wHoyiuWD4VhNJFa7kDYaa4jDslTXGTKr", "rKlzkVJtXTvTvYxD0bW2nzvVC4jiurjxgtqOrXnBcqU15HGP9HtPgmqjNGSlwjCvlAd6aZ3cvcFHXpKV", "asVUlasCqF5MmoLG5qql5Cg2QRRVk0QB0Q6+FqY+ER1PnNLdqeRHylDUWh1ROz8HerneZnWiRWfkyxyJ", - "WAbjszh5DFHjIbx7av8fVanlHMMYrwTGunQf7ZP0Wbk7pslkEJ+5izjNELNrrerVOkrozK5FWQaDgdsG", - "XXsFNB7lJ1NjOBK+2HJTPGUbZazX7Ggk0wzVhnjdz5W0WpVl1whEIvHKW7a/59uzPLcvlbpc8PzyAeqR", - "UtlmpcU8vGfuB+O1M+leKq/uhZdR+fDDqXGpHYameSKZzJB6LOXoQuoRmO8Pc6zDNu6z4cL66+oyr7Ta", - "cCYZt2oj8jQN/76i20Zj0lIsIZkjjGoZUlYHbIaMOr4cmmAGZElDNIPkyWJsZ8zzNO/URebh/osSb39c", - "tgR/SYxcTEM+6aWWLB+VrXoAIKT01NjWmgogxpJPw1XUilIToEu6D+hELo6RP7eDzY1w50BZuBVQg2jD", - "BsD7pOzPKZcbRS4u1DZ8f9Ame7sR8B/3U3mHeYyFVF20pKUpqCokhhnhCOmU0nvjj97gM/PF1Cikpljt", - "xBs1AmA8LqkDw6TopGPBWHJRQpGlah2eNzaheaTZ+qdQ/RLkwnhOnvM6lBp0Y9cafKISEql1199UcUdK", - "qmk+tNzKArZA7yh+Aa2ohuA88ndASSUGe8q3qrISrqATruWzp9Qo2okrCH1N05kVABV6//o2qVQcUnyX", - "9wwVfu1ZFMkyBbtJywUhlnaKHTBLJI0oW5nRMTFTj5KD6EoUNe/gzxwrcnTNbu4oJ1A1kMmzoLdNneYn", - "GuF1GOAs9E+JMgET76fxoaNZUBp1+xjQwbjE2oydepkOS4xTAzUODZytaByfROIt3zAVv5bjBsAhybfq", - "zcR9EkpGiP16CzlKNd24u9vjhOFgzPTSfo2K4LrZ4Zsbkn8TGt5LwqPjpVQNA/6h2h5LTaALL7BjAyw6", - "LZ3Y66RmLCfo+b/nf3O2qMNATq+m6oaxBvcCgscOM5E3zgov0IrmQgvxhXOfiLKvlIsosnrDd0xp/Mfp", - "a/+oeSmWOzyhBH7oxsyaOxLyLkLyXft4RTfxfsFkHgALdgEVpqJ1i6ljRsPt3CgR0O4KDGVoFNvwS4i3", - "Ad3yxHly61iOqRcbYQxedr3tHGLBLz4kE9nwItaRMaVht+B3SHLrev/P9tVWPFXIRFaVPA+1LH0xnY5B", - "nOrVBuKya9jsf9Y3VI8DCTQ1cFui1eEdeHED496RkRupWPmxQiEdsAe1QQc1Um61jIk2yl41iD0PIict", - "5a53YWp8yADouKLgIfDjAoufBv/JbKNjy5gC/j8L3kdKqsbwUvXUT4DlTq6IBKxkV12obaZhaQ6FQpBh", - "1SnCus0yEYyTQuYauKHYkPMfvcrWJtMU0qmQFL3YeN+aUQpYCtkySyGr2iY0AMypKXcRwmLzNKJ1xNkz", - "JiU4MeyKlz9egdaiGNs4dzqo+GBczCCY5H3fhPLf3KnDAYRptR98SQjtS7WombvAqVwSBRYay2XBdRE3", - "F5LloN29z675ztzc9+Gg1bWTLw54P3gkzXTft0d+ECRtAqTcefflLT0TDYD8Dl0UE1wLGMGacCuQUcSq", - "EU/CEIZ0Pg6+zUq1wvdlIwTos5ai74eUFSXRYEvy0HHzGPEL7J8GE7b7g28Vzjpliv3n7EdEHSo8P0lh", - "9540sqb1H/xRRCYdhED/ctWGhdPmDOk/9UbTp+WI32kG4S48Ygh7TeEhNB+MeDK6FtyRXUQHuX/gG5tr", - "pxfC6vrgUy9BSYfNULc1ewK/wbRBzjz3gTtDo89AKSakzP072iNtQmRJDvfACHhUtdyfre60TTCFG+eY", - "6mH7X85mlaqyfEo0INV0KLxB20PahXGEPiJz9ci6m8AJ01Q56WTE6ZQ7ObaA2mi5lUN+mSrfp2SPGTRG", - "OGjXWK6WyMuopjfaYfCNR2O8mPdfH3UNNg2TYJxpyGuNBs1rvjtckGokl/DFX84+f/zkr08+/4K5BqwQ", - "KzBtPupeQac2YkzIvp3l08aIDZZn05sQ3qUT4oKnLDy3aTbFnzXitqZNNjkoZ3WMJTRxASSOY6KQ0I32", - "Csdpg77/ubYrtcg737EUCn79PdOqLNP1ABrRLWHqT+1WZOx3En8F2ghjHSPs+uqEbWNlzRrNcZgV9ory", - "jCiZ+7T9DRUIOxKMk1rIWKgl8jN89ev9Gwy2Vel5Ffkk9q3L60VkEcPgDIzfWACrVOVFabFkKYjwbYmO", - "3lx6QyOGd0bRkw2zpTjKFCH6mOQ06cWllPdz+26ZT5vm9G4TE+JFOJQ3IM0xS/r4i/abcJLWlP5Pwz8S", - "T/TvjGs0y/01eEVSP7hZufZJoA2fayfIAwEYeYfZeUEXPSGKUtRqssqj/T64Ovvix/etC/TggwGEJHQ4", - "AF78sLJt18S4e3B+41yv3zdIiZbyfowSOss/9FYzsN7mIom2yBsprAVDbEkNxcLoIa553rxvHdFKBs9g", - "tVKWOc20LBPPZ8lugmcqJhynEugrXn56rvGN0MaeIT6geD3+aCZ+QxkjmVBpbpbB7SWfNHf0XvLuppav", - "8Mnuf4Lbo+Q954fy7uLBbYZWL6xlvgq3Ar0CZtc4JoUDPf6CLXwZhkpDLkzfDX0dhJPmySBosfShl7C1", - "B94oHlrnz8regoyXIWaE/RC5kxSa7VoI2yP6GzOVkZObpPIU9Q3IIoG/FI+Ky7YeuC5umbL/ZglBotRe", - "RyYEGRaknbo8SnrhLp3awHCdk2/rDm4TF3W7tqnZbCZn/n/37q1dTElCk87S77pjFpw7Sdd/VLL+XyH/", - "DeHIj+HnTVHMz2MZUSnr50jW5t5+1KI8GCDSycH9cT5bgQQjDGaZ/quvKvJp79IAAb3JHx5VgvU2iUQI", - "MYm1diaPpoqya09IrO27JbIh43u3vNbC7rCibDCgib8mM/V822R98FlDGt+Vv/usuoSmqnebI6I24Xb9", - "VvES7yNyqUl3C6nyhH1NuZ/9QfnzvcW/wWd/elo8+uzxvy3+9OjzRzk8/fzLR4/4l0/54y8/ewxP/vT5", - "00fwePnFl4snxZOnTxZPnzz94vMv88+ePl48/eLLf7vn+JADmQANSd+fzf53dlauVHb26jx744BtccIr", - "8R24vUFdeamw4qFDao4nETZclLNn4af/FU7YSa427fDh15mv3DNbW1uZZ6en19fXJ3GX0xU+Cs+sqvP1", - "aZgH69B15JVX5000OcW94I621mPcVE8KZ/jt9dcXb9jZq/OTlmBmz2aPTh6dPPZFjyWvxOzZ7DP8CU/P", - "Gvf9FDMvnhqfVP20qnxa9aSb7LWvxdOluNAZgW3ycrvdpnTdPjm6iSsenxdIW3aY0h0rc2EYFAL45NGj", - "sCte5omunlN8sfDsw2xa/fPhZLjz/WwLi3r1ysEcknw0ad+8c8LjDP2FhLBmv0gF5iuDlnUtrriF2fuP", - "81lVJ9D5NT5HMPtwNo9ShRM0qiwajA8w+qr+/wSjH+ezU88nZ88+uL/WwEtM/+P+2DhCzcMnDbzY+f+b", - "a75agT7x63Q/XT05DRLx6Qef1+Hjvm+ncTTQ6YdO+oviQM8Q7XKoyemHUNh3/4Cdoq4+zjDqMBHQfc1O", - "F1jMZ2pTiFc3vhSkeXP6AZXB0d9PvUUv/RGVcuL2pyGNzEhLShiQ/thB4Qe7dQvZP5xrE42Xc5uv6+r0", - "A/4HyTZaEeUfPbVbeYpBA6cfOojwnweI6P7edo9bXG1UAQE4tVxSNeR9n08/0L/RRLCtQAunEWHOH/8r", - "5WY7xaJ4u+HPO5knfxyuo5OXat/NUmu0TwoTImm66ayS10c/R5a5LbOblnGjn5lrKOwNb/V9K/s4nz29", - "Q67czWeaAOYrXrDw/Brnfvzp5j6XFNHrxBwSxxCCp58Ogs72se9gx35Qln2DdoOP89nnn3InzqXTYnjJ", - "sGVUlHh4RH6Sl1Jdy9DSyfH1ZsP1bvLx6V+jTg5smskVCSqKnuR3j9pZUQyInvQZMPYrhbfrGMY2ZlV5", - "j2GLtFadE9ItYWgPGqDqDdXm7iW5o1RMQZCQqoBZrGhZXcPHW/KEXqwR1/Y8Yd5EOz0G+S9DGfEI1GTG", - "tn4kBo08VMUPkXBb6b6Njf+Dp/zBUxqe8vmjzz7d9Begr0QO7A1sKqW5FuWO/SSbRxc35nFnRZFMc9k9", - "+gd53Hy2zXJVwApk5hlYtlDFztcNmXUmuASy3AwEmdNg6ehoDCPcM9hQUtJKGwo8e/Y25aL3paerelGK", - "nJGVF80cToePrBBN3sEu85tH2zpgP4nc1qwQZd28gbfXyr8xHV4o7H6cGcL8Q+PFgwdR2B27FrJQ11iS", - "H8H9Rw3I5z28YZpZAsAo3nRYxqV1XjkAB2CNzYderynY2TP5S36zuUt+7NTvb3llHbxMm7xe/3Hx4w/R", - "SzSyNFBwCr6DItLFoHWtMBj7mmN0IpV7e042oHLHpEL/Vm06laZO/riH/uD9t+f93zaJXqnGlMXiMUOW", - "FN0FJ5ME3iRv/9D509stZhQanErP6n5nnK2wPuDwglrs2PmLgfZK3fpXwlc7bNq7FRL8vg/iUYx/hL3s", - "E2ncQlbKNgHStKg/hMw/hMxbKa6TD88U3TVpWaKqnXygj81DAc7OIxRMsYzhRgNQptifftPjeycbP7Rt", - "pWxZlAoaChZ9oMwEfTT/wSL+YBG3YxHfQuIw4qn1TCNBdMfZuqYyDExAU3TC/YLUEZrXJdfRY9BDJuwz", - "HDGtCv4qXONTG+ySuCJ7HcawCwreTGzg3drw/mB5f7C83w/LOzvMaLqCya2tXpew2/CqsXWZdW0LdR15", - "yBEWCrwe+vhI8e//fXrNhc2WSvvCInxpQQ87W+Dlqa8i3Pu1Ldw3+ILVCKMf4xReyV9Peddp2XWcO9Y7", - "1nHgVU999Y7jkUbh/Xz43MaPxfFYyPabSKy37x3LNqCvwo3Qhhc9Oz3FhCprZezp7OP8Qy/0KP74viGP", - "D8094snkI9KF0mIlJC8zHxvRlkKfPTl5NPv4/wIAAP//Vd+WO7oZAQA=", + "WAbjszh5DFHjIbx7av+nefNSbJFuQKeO/JJZXcOc+Rb9Gtn+4HMNbCOMIVAaWroWZYkPx8U28rw2gQtp", + "1I6IvecYVnklMPamm0SApOHK3XlNZoWYB1zEaY+YXWtVr9ZRgukGzqDy6torxPEoP5kaw6PwBZmb4inb", + "KGO9pkkjtUtuQ87u50parcqya5QiEX3lLe3f8+1ZntuXSl0ueH75APVaqWyz0mIe3lf3gwPbmXQvtVj3", + "As6onPnhVL3UDkPlPNFOZpA9Fnd0YfcIzPeHOehhm/vZcGH9dXWZaVqNOZOMW7URefpM/b6i7UZj5FIs", + "KpmzjGorUpYJbIaHPb6smuAKZJFDNIPkyeJwZ8wzAu9kRnbj/osSeH9ctgTPaEYuyiFz8VJUlo/Kej0A", + "EFJ6+mxrTQUZY0ms4SpqRakS0EXeB3TirYKRSLeDzY1w50BZuBVQg+jHBsD7ZHyYU245iqRcqG34/qBN", + "Pncj4D/up/IO8xgL8bpoSUtTkFdIVDPCEdIprvfGQ73BZ++LqVFRTfHciTd8BMB4nFQHhknRUseCseSi", + "hCJL1V48b2xU80jT9k+z+iXRhfGcPOd1KH3oxq41+MQpJOLrrv+r4o6UVNN8aEmWBWyB3nX8AlpRTcN5", + "5H+Bkkoe9owBqspKuIJO+JjP5lKjqCmuIPQ1TWdWAFTojezbyFJxUfFd3jOc+LVnUWTNFOwmLSmEWNop", + "dsBMkjTqbGVGx8RMPUoOoitR1LyDP3OsyNE1A7qjnEDVQEfIgh45dZqfaITXYYCz0D8lygRMvJ/Gh45m", + "QWnU7WNAB+MkazN26mU6TDJOVdQ4WHC2onHEEom3fMNU/FqOGySHJN+qWxP3SSgZIfbrLeQo1Xh9Bwqv", + "8Yw4KXzWE6R2CVCQVuC6JKzta5BMqqjE5DU3jarS5lAMP9DE2EhIr03fwKncRjPefmcZDsZML5naqCKh", + "Gzq9uXn+NzmJew/i6HgpGjHgn//tsX8F6vZqBzbAUt7S7aeT/bFIo7/FPBefs0UdBipLdU01I2M99AUE", + "PyhRX3ABebFcNNdyiNqc+/SefVOHiOLVN3zHlMZ/nNb5j5qXYrlDPkPgh27MrLkjIe94pYgAHwXqJt4v", + "Xs0DYMHaosJUtG4xdcxouJ0bJQLaXeShuI9iG34J8TZgsAPxz9w6xmnqBVou3JXd284hFvziQ4qWDS9i", + "TR8TRXbLqIfUwa73/2zfwsVThfxuVcnzUCHUlyjq8hmsAhyIy65hs/+x5JCvBRJoKgu3RKvD6/riBibT", + "I1lX6gXCWPmVDtiDiquDyjO3WsZEy2+vxsaeZ6aTlnLXuzA16mYAdFyn8RD4cdnKT4P/ZA7XsWVMAf+f", + "Be8jhWpjeKkm7SfAcicDRwJWslYv1DbTsDSHAkzIXO3Ued3m7ggmViFzDdxQxM35j17xbFOUCukUYYoJ", + "bXyazSgFLIVsmaWQVW0TegxmKpW7CGGx0R/ROuJCG5MSnDB5xcsfr0BrUYxtnDsdVNIxLhERHB2+b8KE", + "0dypwwGEaXU4fJ/ZmtHjZu4CpyJUFK5pLJcF10XcXEiWg3b3PrvmO3Nzj1LjHDjkU+KRNNPNGhB5l5C0", + "CZBy553Ct/T3NADyO3T8THDYYFxwwllDph2rRvwzQxh+Fw6bDd9mpVrhK8KRA+Fz06KHj1RAJdEMTvLZ", + "tHWHeYz4BfZPg2n5PSOyCmedMsX+c/8jbiWqkT9JYfeefLJR9p91UtwtHcyAVLlqg/+JWIbnMfUS1ydf", + "iV/jBmEzPFUJtAfRJsKIf6hrFx/ZRQyD8M+4YyP49HJn3UiL1HtfsgxkaDEwe8L7wbSh7Dz34VlDU9rA", + "1EBImfvX0kda2sg+H+6lEfCoNr0/691pm5AZN84xNeL2v4/OKlVl+ZSYT6rcUXg3gYe0C+MIfUROgJF1", + "N+Expqll08l71Clqc2yZvNGiOoe8XVW+T+kfMxONcPSuC0ItkZdR5Xa0buFLnsaYMu+/MeuawRomwTjT", + "kNcazcTXfHe47NhIxuiLv5x9/vjJX598/gVzDVghVmDarOO9sl1tXKCQfbvPp40EHCzPpjchZB8gxAX/", + "Y3hU1WyKP2vEbU2bUnRQtOwY+3LiAkgcx0S5qBvtFY7Thvb/c21XapF3vmMpFPz6e6ZVWaarPjRyVcKB", + "ktqtyIXiNJAKtBHGOkbY9YAK20ZEmzWaBzH37xVlk1Eyh2A/9lQg7EjIVWohYwG1yM/wbbf3GjHYVqXn", + "VeTp2bcur6eRhQ6FRoyKWQCrVOVFe7FkKYjwBZGOXtZ6wydaxKMY2YbZUrRsihB95Hma9OKC2fu5fbeY", + "q01zereJCfEiHMobkOaYf2I8b8FNOElr2v+n4R+JRAx3xjWa5f4avCKpH9ysKP8k0IaP8hPkgQCMvLbt", + "vJOMHopFiYg1eQnQnxAcyH3x4/vWsXzwWQhCEjocAC9+Ptu2a14yeHB+44y+3zdIiZbyfowSOss/9CI3", + "sN7mIom2yBtNrAVDbEkNxcLoubV53rxiHtFKBo+dtVKWOc20LBOPpMmOg2cqJhynEugrXn56rvGN0Mae", + "IT6geD3+NCp+KRsjmVBpbpan7yWfNHf0Kvbuppav8GH2f4Lbo+Q954fyTvjBbYbGHaxYvwq3Ar31Ztc4", + "JgVZPf6CLXyxjUpDLkzfuX8dhJPmYShosfQBrbC1B16iHlrnz8regoyXIRKH/RC5txqfvYewPaK/MVMZ", + "OblJKk9R34AsEvhL8ai4OO+B6+KWhRlulvYlSuB2ZNqXYdnhqcuj1Cbu0qkNDNc5+bbu4DZxUbdrm5qz", + "aHJ9h3fv3trFlFRD6VoMrjvmOrqTogxHlWT4FbIcEY78GH7eFMX8PJb3lnK7juTm7u1HLcqDASudTOsf", + "57MVSDDCYC7xv/raMZ/2Lg0QUOaF4VElWG+TLoYQk1hrZ/JoqiiH+oT06b5bIuc1vmrMay3sDusGBwOa", + "+GsyH9O3TW4Pnxum8aX5u8+qS2hqt7eZQGoTbtdvFS/xPiIXn3S3kCpP2NeU4dsflD/fW/wbfPanp8Wj", + "zx7/2+JPjz5/lMPTz7989Ih/+ZQ//vKzx/DkT58/fQSPl198uXhSPHn6ZPH0ydMvPv8y/+zp48XTL778", + "t3uODzmQCdCQ2v/Z7H9nZ+VKZWevzrM3DtgWJ7wS34HbG9SVlwrrWjqk5ngSYcNFOXsWfvpf4YSd5GrT", + "Dh9+nfn6TLO1tZV5dnp6fX19Enc5XeHT/8yqOl+fhnmw2mBHXnl13sToUxwO7mhrPcZN9aRwht9ef33x", + "hp29Oj9pCWb2bPbo5NHJY1/aWvJKzJ7NPsOf8PSscd9PMb/mqfGp80+ryifPT7rtXvuKS12KC50R2Cb7", + "utttSsruU+CbuK71eYG0ZYeJ+7H+GoZlIYBPHj0Ku+JlnujqOcV3IM8+zKZVuR9Ohjvfz6mxqFevHMwh", + "lUuT3M87JzzO0H9JCGv2i1RgvjJoWdfiiluYvf84n1V1Ap1f4yMPsw9n8yghPEGjyqLB+ACjr+r/TzD6", + "cT479Xxy9uyD+2sNvMQkT+6PjSPUPHzSwIud/7+55qsV6BO/TvfT1ZPTIBGffvDZOz7u+3YaRyedfugk", + "OSkO9AzRN4eanH4I5Zv3D9gp3evjHqMOEwHd1+x0gSWbpjaFeHXjS0GaN6cfUBkc/f3UW/TSH1EpJ25/", + "GpIFjbSktBDpjx0UfrBbt5D9w7k20Xg5t/m6rk4/4H+QbKMVUZbZU7uVpxjEcPqhgwj/eYCI7u9t97jF", + "1UYVEIBTyyXVvN73+fQD/RtNBNsKtHAaEWZ28r9SBr5TLH24G/68k3nyx+E6OtnH9t0stUb7pDAhsqeb", + "tCx5ffQzoZnbMrtpeVX6+deGwt7wVt+3so/z2dM75MrdrLUJYL7iBQuP7HHux59u7nNJEcZOzCFxDCF4", + "+ukg6Gwf+w527Adl2TdoN/g4n33+KXfiXDothpcMW0alp4dH5Cd5KdW1DC2dHF9vNlzvJh+f/jXq5MCm", + "mVyRoKIo8UL3qJ0VxYDoSZ8BY79SeLuOYWxjVpX3GLZIa9U5Id0ShvagAareUAX2XipDSrgVBAmpCpjF", + "ipbVNXy8JU/oxRpxbc8T5k200+Ojg2UoFh+BmszL14/EoJGHqvghEj5/ESZtY/X/4Cl/8JSGp3z+6LNP", + "N/0F6CuRA3sDm0pprkW5Yz/J5hHIjXncWVEkk5l2j/5BHjefbbNcFbACmXkGli1UsfPVYWadCS6BLDcD", + "QeY0WDo6GsMI9ww2lJS00oYmz569Tbno/VO7ql6UImdk5UUzh9PhIytEk12yy/zm0bYO2E8igzkrRFk3", + "mQXstfIvd4cXCrsf59sw/9B48eBBFHbHroUs1PWDkwDuP2pAPu/hDdPMEgBG8abDYj2t88oBOABrbD70", + "ek3Bzp7JX/KbzV3yY6d+f8sr6+Bl2mRv+4+LH3+IXsaRpYGCU/BdFpEuBtFrhcHh1xyjE6mo33OyAZU7", + "fOFpua1Np57YyR/30B+8//a8/9smnS9VErNYImjIkqK74GSSwJvk7R86f3q7xYxCg1NJeN3vjLMVVoEc", + "XlCLHTt/MdBeqVv/Svhqh017t0KC3/dBPIrxj7CXfSKNW8hK2SZAmhb1h5D5h5B5K8V18uGZorsmLUtU", + "m5UP9LF5KLPaeYSCibQx3GgAyhT70296fO9k44e2rZQtixJ+Q8GiD5QpoY/mP1jEHyzidiziW0gcRjy1", + "nmkkiO44W9dUhoFpfYpOuF+QOkLzuuQ6epx6yIR9hiOmVcFfhWt8aoNdEldkr8MYdkHBm4kNvFsb3h8s", + "7w+W9/theWeHGU1XMLm11esSdhteNbYus65toa4jDznCQoHXQx8fKf79v0+vubDZUmlfPoYvLehhZwu8", + "PPW1onu/tuUZB1+w5mT0Y5wYLfnrKe86LbuOc8d6xzoOvOqpr95xPNIovOcPn9v4sTgeC9l+E4n19r1j", + "2Qb0VbgR2vCiZ6enmOBlrYw9nX2cf+iFHsUf3zfk8aG5RzyZfES6UFqshORl5mMj2oL3sycnj2Yf/18A", + "AAD//wm+5emgGwEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 4548badf2c..0ada10df52 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -178,16 +178,16 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "H4sIAAAAAAAC/+x9/XPctpLgv4Kb3Srb2qEkfyT74qvUnmInedrYsctSsvvW8iUYsmcGTyTAB4DzEZ//", - "9ys0ABIkwRmOJNvJbn6yNSSBRqPR6O9+P0lFUQoOXKvJ0/eTkkpagAaJf9E0FRXXCcvMXxmoVLJSM8En", + "9ys0ABIkwRmOpNjxbn6yNSSBRqPR6O9+P0lFUQoOXKvJ0/eTkkpagAaJf9E0FRXXCcvMXxmoVLJSM8En", "T/0zorRkfDGZTpj5taR6OZlOOC2gecd8P51I+EfFJGSTp1pWMJ2odAkFNQPrbWnerkfaJAuRuCHO7BDn", "zycfdjygWSZBqT6Ur3i+JYyneZUB0ZJyRVPzSJE100uil0wR9zFhnAgORMyJXrZeJnMGeaaO/SL/UYHc", "Bqt0kw8v6UMDYiJFDn04n4lixjh4qKAGqt4QogXJYI4vLakmZgYDq39RC6KAynRJ5kLuAdUCEcILvCom", "T99OFPAMJO5WCmyF/51LgN8g0VQuQE/eTWOLm2uQiWZFZGnnDvsSVJVrRfBdXOOCrYAT89UxeVkpTWZA", "KCdvvntGHj9+/JVZSEG1hswR2eCqmtnDNdnPJ08nGdXgH/dpjeYLISnPkvr9N989w/kv3ALHvkWVgvhh", - "OTNPyPnzoQX4DyMkxLiGBe5Di/rNF5FD0fw8g7mQMHJP7Mt3uinh/J91V1Kq02UpGNeRfSH4lNjHUR4W", + "OTNPyPnzoQX4DyMkxLiGBe5Di/rNF5FD0fw8g7mQMHJP7Mt3uinh/J90V1Kq02UpGNeRfSH4lNjHUR4W", "fL6Lh9UAtN4vDaakGfTtafLVu/cPpw9PP/zT27Pkv9yfXzz+MHL5z+px92Ag+mJaSQk83SYLCRRPy5Ly", "Pj7eOHpQS1HlGVnSFW4+LZDVu2+J+dayzhXNK0MnLJXiLF8IRagjowzmtMo18ROTiueGTZnRHLUTpkgp", - "xYplkE0N910vWbokKVV2CHyPrFmeGxqsFGRDtBZf3Y7D9CFEiYHrRvjABf1+kdGsaw8mYIPcIElzoSDR", + "xYplkE0N910vWbokKVV2CHyPrFmeGxqsFGRDtBZf3Y7D9CFEiYHrRvjABf1xkdGsaw8mYIPcIElzoSDR", "Ys/15G8cyjMSXijNXaUOu6zI5RIITm4e2MsWcccNTef5lmjc14xQRSjxV9OUsDnZioqscXNydo3fu9UY", "rBXEIA03p3WPmsM7hL4eMiLImwmRA+WIPH/u+ijjc7aoJCiyXoJeujtPgioFV0DE7O+QarPt/37x6kci", "JHkJStEFvKbpNQGeigyyY3I+J1zogDQcLSEOzZdD63BwxS75vythaKJQi5Km1/EbPWcFi6zqJd2woioI", @@ -202,212 +202,213 @@ var swaggerSpec = []string{ "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", - "8aePjk8nH/5/AAAA//9iOfQe9x8BAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index f338c38b1f..4a97d94d07 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1210,6 +1210,7 @@ type PreEncodedSimulateTxnResult struct { LogicSigBudgetConsumed *uint64 `codec:"logic-sig-budget-consumed,omitempty"` TransactionTrace *model.SimulationTransactionExecTrace `codec:"exec-trace,omitempty"` UnnamedResourcesAccessed *model.SimulateUnnamedResourcesAccessed `codec:"unnamed-resources-accessed,omitempty"` + FixedSigner *string `codec:"fixed-signer,omitempty"` } // PreEncodedSimulateTxnGroupResult mirrors model.SimulateTransactionGroupResult @@ -1246,6 +1247,7 @@ type PreEncodedSimulateRequest struct { AllowUnnamedResources bool `codec:"allow-unnamed-resources,omitempty"` ExtraOpcodeBudget uint64 `codec:"extra-opcode-budget,omitempty"` ExecTraceConfig simulation.ExecTraceConfig `codec:"exec-trace-config,omitempty"` + FixSigners bool `codec:"fix-signers,omitempty"` } // SimulateTransaction simulates broadcasting a raw transaction to the network, returning relevant simulation results. diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index ee306a62b4..6f36784ee4 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -473,13 +473,20 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra } func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResult { - return PreEncodedSimulateTxnResult{ + result := PreEncodedSimulateTxnResult{ Txn: ConvertInnerTxn(&txnResult.Txn), AppBudgetConsumed: omitEmpty(txnResult.AppBudgetConsumed), LogicSigBudgetConsumed: omitEmpty(txnResult.LogicSigBudgetConsumed), TransactionTrace: convertTxnTrace(txnResult.Trace), UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed), } + + if !txnResult.FixedSigner.IsZero() { + fixedSigner := txnResult.FixedSigner.String() + result.FixedSigner = &fixedSigner + } + + return result } func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker) *model.SimulateUnnamedResourcesAccessed { @@ -588,6 +595,7 @@ func convertSimulationResult(result simulation.Result) PreEncodedSimulateRespons MaxLogSize: result.EvalOverrides.MaxLogSize, MaxLogCalls: result.EvalOverrides.MaxLogCalls, ExtraOpcodeBudget: omitEmpty(result.EvalOverrides.ExtraOpcodeBudget), + FixSigners: omitEmpty(result.EvalOverrides.FixSigners), } } @@ -614,6 +622,7 @@ func convertSimulationRequest(request PreEncodedSimulateRequest) simulation.Requ AllowUnnamedResources: request.AllowUnnamedResources, ExtraOpcodeBudget: request.ExtraOpcodeBudget, TraceConfig: request.ExecTraceConfig, + FixSigners: request.FixSigners, } } diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 41482e72e2..0399c8215a 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -8896,3 +8896,451 @@ func TestUnnamedResourcesCrossProductLimits(t *testing.T) { }) } } + +func TestFixSigners(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + t.Run("AllowEmptySignatures=false", func(t *testing.T) { + t.Parallel() + env := simulationtesting.PrepareSimulatorTest(t) + defer env.Close() + + sender := env.Accounts[0] + + txn := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + }).SignedTxn() + + simRequest := simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{ + {txn}, + }, + AllowEmptySignatures: false, + FixSigners: true, + } + + _, err := simulation.MakeSimulator(env.Ledger, false).Simulate(simRequest) + require.ErrorAs(t, err, &simulation.InvalidRequestError{}) + require.ErrorContains(t, err, "FixSigners requires AllowEmptySignatures to be enabled") + }) + + type testInputs struct { + txgroup []transactions.SignedTxn + sender simulationtesting.Account + other simulationtesting.Account + innerRekeyAddr basics.Address + } + + makeTestInputs := func(env *simulationtesting.Environment) testInputs { + sender := env.Accounts[0] + other := env.Accounts[1] + + innerRekeyAddr := env.Accounts[2].Addr + innerProgram := fmt.Sprintf(`#pragma version 9 + txn ApplicationID + bz end + + // Rekey to the the innerRekeyAddr + itxn_begin + int pay + itxn_field TypeEnum + txn ApplicationArgs 0 + itxn_field Sender + addr %s + itxn_field RekeyTo + itxn_submit + + end: + int 1 + `, innerRekeyAddr) + + innerAppID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: innerProgram, + ClearStateProgram: "#pragma version 9\nint 1", + }) + + outerProgram := fmt.Sprintf(`#pragma version 9 + txn ApplicationID + bz end + + // Rekey to inner app + itxn_begin + int pay + itxn_field TypeEnum + txn ApplicationArgs 0 + itxn_field Sender + addr %s + itxn_field RekeyTo + itxn_submit + + // Call inner app + itxn_begin + int appl + itxn_field TypeEnum + int %d + itxn_field ApplicationID + txn ApplicationArgs 0 + itxn_field ApplicationArgs + itxn_submit + + end: + int 1`, innerAppID.Address(), innerAppID) + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: outerProgram, + ClearStateProgram: "#pragma version 9\nint 1", + }) + + env.TransferAlgos(sender.Addr, appID.Address(), 1_000_000) + + // rekey to EOA + pay0 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + RekeyTo: other.Addr, + }) + // rekey to outer app, which rekeys to inner app, which rekeys to another app + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + RekeyTo: appID.Address(), + }) + // app rekeys to random address + appCall := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: other.Addr, + ApplicationID: appID, + ApplicationArgs: [][]byte{sender.Addr[:]}, + ForeignApps: []basics.AppIndex{innerAppID}, + }) + // rekey back to sender (original address) + pay2 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + RekeyTo: sender.Addr, + }) + // send txn from sender + pay3 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: sender.Addr, + Receiver: sender.Addr, + }) + + txgroup := txntest.Group(&pay0, &pay1, &appCall, &pay2, &pay3) + + return testInputs{ + txgroup: txgroup, + sender: sender, + other: other, + innerRekeyAddr: innerRekeyAddr, + } + } + + // Convenience function for getting the expected app call result. This is a function instead of + // a variable because it's used by multiple tests, and the expected result is modified with the + // input transactions before comparison by each test. + expectedAppCallResultFn := func() simulation.TxnResult { + return simulation.TxnResult{ + AppBudgetConsumed: ignoreAppBudgetConsumed, + Txn: transactions.SignedTxnWithAD{ + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + InnerTxns: []transactions.SignedTxnWithAD{ + {}, + { + ApplyData: transactions.ApplyData{ + EvalDelta: transactions.EvalDelta{ + InnerTxns: []transactions.SignedTxnWithAD{ + {}, + }, + }, + }, + }, + }, + }, + }, + }, + } + } + + t.Run("no signatures", func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + inputs := makeTestInputs(&env) + + // Do not sign any of the transactions + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{inputs.txgroup}, + AllowEmptySignatures: true, + FixSigners: true, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + FixSigners: true, + }, + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + {}, // pay0 + { // pay1 + FixedSigner: inputs.other.Addr, + }, + // appCall + expectedAppCallResultFn(), + { // pay2 + FixedSigner: inputs.innerRekeyAddr, + }, + {}, // pay3 + }, + AppBudgetConsumed: ignoreAppBudgetConsumed, + AppBudgetAdded: 2800, + }, + }, + }, + } + }) + }) + + t.Run("sign pay after outer rekey", func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + inputs := makeTestInputs(&env) + + // Sign txn 1, payment after the outer rekey, with the wrong AuthAddr. This renders the + // group invalid, since the AuthAddr will not be corrected if a signature is provided. + inputs.txgroup[1] = inputs.txgroup[1].Txn.Sign(inputs.sender.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{inputs.txgroup}, + AllowEmptySignatures: true, + FixSigners: true, + }, + expectedError: fmt.Sprintf("should have been authorized by %s but was actually authorized by %s", inputs.other.Addr, inputs.sender.Addr), + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + FixSigners: true, + }, + TxnGroups: []simulation.TxnGroupResult{ + { + FailedAt: simulation.TxnPath{1}, + Txns: []simulation.TxnResult{ + {}, // pay0 + {}, // pay1, does NOT contain FixedSigner + {}, // appCall + {}, // pay2 + {}, // pay3 + }, + AppBudgetConsumed: 0, + // This is here even though we don't make it to the app call because + // pooled app budget is determined before the group is evaluated. + AppBudgetAdded: 700, + }, + }, + }, + } + }) + }) + + t.Run("sign pay after inner rekey", func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + inputs := makeTestInputs(&env) + + // Sign txn 3, payment after the inner rekey, with the wrong AuthAddr. This renders the + // group invalid, since the AuthAddr will not be corrected if a signature is provided. + inputs.txgroup[3] = inputs.txgroup[3].Txn.Sign(inputs.other.Sk) + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{inputs.txgroup}, + AllowEmptySignatures: true, + FixSigners: true, + }, + expectedError: fmt.Sprintf("should have been authorized by %s but was actually authorized by %s", inputs.innerRekeyAddr, inputs.other.Addr), + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + FixSigners: true, + }, + TxnGroups: []simulation.TxnGroupResult{ + { + FailedAt: simulation.TxnPath{3}, + Txns: []simulation.TxnResult{ + {}, // pay0 + { // pay1 + FixedSigner: inputs.other.Addr, + }, + // appCall + expectedAppCallResultFn(), + {}, // pay2, does NOT contained FixedSigner + {}, // pay3 + }, + AppBudgetConsumed: ignoreAppBudgetConsumed, + AppBudgetAdded: 2800, + }, + }, + }, + } + }) + }) + + // Edge case tests below + + t.Run("sender account is empty", func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + sender := env.Accounts[0] + + appID := env.CreateApp(sender.Addr, simulationtesting.AppParams{ + ApprovalProgram: "#pragma version 9\nint 1", + ClearStateProgram: "#pragma version 9\nint 1", + }) + + var noBalanceAccount1 basics.Address + crypto.RandBytes(noBalanceAccount1[:]) + + var noBalanceAccount2 basics.Address + crypto.RandBytes(noBalanceAccount2[:]) + + noBalPay1 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: noBalanceAccount1, + Receiver: noBalanceAccount1, + Fee: 0, + Note: []byte{1}, + }) + appCall := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: sender.Addr, + ApplicationID: appID, + Fee: env.TxnInfo.CurrentProtocolParams().MinTxnFee * 3, + }) + noBalPay2 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: noBalanceAccount2, + Receiver: noBalanceAccount2, + Fee: 0, + Note: []byte{2}, + }) + txgroup := txntest.Group(&noBalPay1, &appCall, &noBalPay2) + + // Testing that our ledger lookup of accounts to retreive their AuthAddr does not crash + // and burn when the account is empty. + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{txgroup}, + AllowEmptySignatures: true, + FixSigners: true, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + FixSigners: true, + }, + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + {}, // noBalPay1 + { // appCall + AppBudgetConsumed: ignoreAppBudgetConsumed, + }, + {}, // noBalPay2 + }, + AppBudgetAdded: 700, + AppBudgetConsumed: ignoreAppBudgetConsumed, + }, + }, + }, + } + }) + }) + + t.Run("fixed AuthAddr is sender address", func(t *testing.T) { + t.Parallel() + simulationTest(t, func(env simulationtesting.Environment) simulationTestCase { + acct0 := env.Accounts[0] + acct1 := env.Accounts[1] + acct2 := env.Accounts[2] + + appID := env.CreateApp(acct0.Addr, simulationtesting.AppParams{ + ApprovalProgram: "#pragma version 9\nint 1", + ClearStateProgram: "#pragma version 9\nint 1", + }) + + pay1 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: acct1.Addr, + Receiver: acct1.Addr, + Note: []byte{1}, + }) + appCall := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: acct0.Addr, + ApplicationID: appID, + }) + pay2 := env.TxnInfo.NewTxn(txntest.Txn{ + Type: protocol.PaymentTx, + Sender: acct1.Addr, + Receiver: acct1.Addr, + Note: []byte{2}, + }) + txgroup := txntest.Group(&pay1, &appCall, &pay2) + + txgroup[0].AuthAddr = acct2.Addr + txgroup[2].AuthAddr = acct2.Addr + + return simulationTestCase{ + input: simulation.Request{ + TxnGroups: [][]transactions.SignedTxn{txgroup}, + AllowEmptySignatures: true, + FixSigners: true, + }, + expected: simulation.Result{ + Version: simulation.ResultLatestVersion, + LastRound: env.TxnInfo.LatestRound(), + EvalOverrides: simulation.ResultEvalOverrides{ + AllowEmptySignatures: true, + FixSigners: true, + }, + TxnGroups: []simulation.TxnGroupResult{ + { + Txns: []simulation.TxnResult{ + { // pay1 + FixedSigner: acct1.Addr, + }, + { // appCall + AppBudgetConsumed: ignoreAppBudgetConsumed, + }, + { // pay2 + FixedSigner: acct1.Addr, + }, + }, + AppBudgetAdded: 700, + AppBudgetConsumed: ignoreAppBudgetConsumed, + }, + }, + }, + } + }) + }) +} diff --git a/ledger/simulation/simulator.go b/ledger/simulation/simulator.go index c7c722686d..d4ed4e4e94 100644 --- a/ledger/simulation/simulator.go +++ b/ledger/simulation/simulator.go @@ -41,6 +41,7 @@ type Request struct { AllowUnnamedResources bool ExtraOpcodeBudget uint64 TraceConfig ExecTraceConfig + FixSigners bool } // simulatorLedger patches the ledger interface to use a constant latest round. @@ -142,7 +143,7 @@ var proxySigner = crypto.PrivateKey{ // check verifies that the transaction is well-formed and has valid or missing signatures. // An invalid transaction group error is returned if the transaction is not well-formed or there are invalid signatures. // To make things easier, we support submitting unsigned transactions and will respond whether signatures are missing. -func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.SignedTxn, tracer logic.EvalTracer, overrides ResultEvalOverrides) error { +func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.SignedTxnWithAD, tracer logic.EvalTracer, overrides ResultEvalOverrides) error { proxySignerSecrets, err := crypto.SecretKeyToSignatureSecrets(proxySigner) if err != nil { return err @@ -158,7 +159,8 @@ func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.Sig // denoting that a LogicSig's delegation signature is omitted, e.g. by setting all the bits of // the signature. txnsToVerify := make([]transactions.SignedTxn, len(txgroup)) - for i, stxn := range txgroup { + for i, stxnad := range txgroup { + stxn := stxnad.SignedTxn if stxn.Txn.Type == protocol.StateProofTx { return errors.New("cannot simulate StateProof transactions") } @@ -181,16 +183,14 @@ func (s Simulator) check(hdr bookkeeping.BlockHeader, txgroup []transactions.Sig return err } -func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.SignedTxn, tracer logic.EvalTracer) (*ledgercore.ValidatedBlock, error) { +func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, group []transactions.SignedTxnWithAD, tracer logic.EvalTracer) (*ledgercore.ValidatedBlock, error) { // s.ledger has 'StartEvaluator' because *data.Ledger is embedded in the simulatorLedger // and data.Ledger embeds *ledger.Ledger - eval, err := s.ledger.StartEvaluator(hdr, len(stxns), 0, tracer) + eval, err := s.ledger.StartEvaluator(hdr, len(group), 0, tracer) if err != nil { return nil, err } - group := transactions.WrapSignedTxnsWithAD(stxns) - err = eval.TransactionGroup(group) if err != nil { return nil, EvalFailureError{SimulatorError{err}} @@ -208,7 +208,7 @@ func (s Simulator) evaluate(hdr bookkeeping.BlockHeader, stxns []transactions.Si return &vb, nil } -func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer logic.EvalTracer, overrides ResultEvalOverrides) (*ledgercore.ValidatedBlock, error) { +func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxnWithAD, tracer logic.EvalTracer, overrides ResultEvalOverrides) (*ledgercore.ValidatedBlock, error) { prevBlockHdr, err := s.ledger.BlockHdr(s.ledger.start) if err != nil { return nil, err @@ -216,6 +216,48 @@ func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer l nextBlock := bookkeeping.MakeBlock(prevBlockHdr) hdr := nextBlock.BlockHeader + if overrides.FixSigners { + // Map of rekeys for senders in the group + staticRekeys := make(map[basics.Address]basics.Address) + + for i := range txgroup { + stxn := &txgroup[i].SignedTxn + sender := stxn.Txn.Sender + + if authAddr, ok := staticRekeys[sender]; ok && txnHasNoSignature(*stxn) { + // If there is a static rekey for the sender set the auth addr to that address + stxn.AuthAddr = authAddr + if stxn.AuthAddr == sender { + stxn.AuthAddr = basics.Address{} + } + } else { + // Otherwise lookup the sender's account and set the txn auth addr to the account's auth addr + if txnHasNoSignature(*stxn) { + var data ledgercore.AccountData + data, _, _, err = s.ledger.LookupAccount(s.ledger.start, sender) + if err != nil { + return nil, err + } + + stxn.AuthAddr = data.AuthAddr + if stxn.AuthAddr == sender { + stxn.AuthAddr = basics.Address{} + } + } + } + + // Stop processing transactions after the first application because auth addr correction will be done in AfterProgram + if stxn.Txn.Type == protocol.ApplicationCallTx { + break + } + + if stxn.Txn.RekeyTo != (basics.Address{}) { + staticRekeys[sender] = stxn.Txn.RekeyTo + } + } + + } + // check that the transaction is well-formed and mark whether signatures are missing err = s.check(hdr, txgroup, tracer, overrides) if err != nil { @@ -239,6 +281,14 @@ func (s Simulator) simulateWithTracer(txgroup []transactions.SignedTxn, tracer l // Simulate simulates a transaction group using the simulator. Will error if the transaction group is not well-formed. func (s Simulator) Simulate(simulateRequest Request) (Result, error) { + if simulateRequest.FixSigners && !simulateRequest.AllowEmptySignatures { + return Result{}, InvalidRequestError{ + SimulatorError{ + errors.New("FixSigners requires AllowEmptySignatures to be enabled"), + }, + } + } + if simulateRequest.Round != 0 { s.ledger.start = simulateRequest.Round } else { @@ -246,11 +296,6 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { s.ledger.start = s.ledger.Ledger.Latest() } - simulatorTracer, err := makeEvalTracer(s.ledger.start, simulateRequest, s.developerAPI) - if err != nil { - return Result{}, err - } - if len(simulateRequest.TxnGroups) != 1 { return Result{}, InvalidRequestError{ SimulatorError{ @@ -259,7 +304,14 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { } } - block, err := s.simulateWithTracer(simulateRequest.TxnGroups[0], simulatorTracer, simulatorTracer.result.EvalOverrides) + group := transactions.WrapSignedTxnsWithAD(simulateRequest.TxnGroups[0]) + + simulatorTracer, err := makeEvalTracer(s.ledger.start, group, simulateRequest, s.developerAPI) + if err != nil { + return Result{}, err + } + + block, err := s.simulateWithTracer(group, simulatorTracer, simulatorTracer.result.EvalOverrides) if err != nil { var verifyError *verify.TxGroupError switch { @@ -304,5 +356,25 @@ func (s Simulator) Simulate(simulateRequest Request) (Result, error) { } simulatorTracer.result.TxnGroups[0].AppBudgetConsumed = totalCost + // Set the FixedSigner for each transaction that had a signer change during evaluation + for i := range simulatorTracer.result.TxnGroups[0].Txns { + sender := simulatorTracer.result.TxnGroups[0].Txns[i].Txn.Txn.Sender + inputSigner := simulatorTracer.result.TxnGroups[0].Txns[i].Txn.AuthAddr + if inputSigner.IsZero() { + // A zero AuthAddr indicates the sender is the signer + inputSigner = sender + } + + actualSigner := simulatorTracer.groups[0][i].SignedTxn.AuthAddr + if actualSigner.IsZero() { + // A zero AuthAddr indicates the sender is the signer + actualSigner = sender + } + + if inputSigner != actualSigner { + simulatorTracer.result.TxnGroups[0].Txns[i].FixedSigner = actualSigner + } + } + return *simulatorTracer.result, nil } diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 95cafe12e9..e5a126a29a 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -142,7 +142,7 @@ int 1`, mockTracer := &mocktracer.Tracer{} s.ledger.start = s.ledger.Ledger.Latest() // Set starting round for simulation - block, err := s.simulateWithTracer(txgroup, mockTracer, ResultEvalOverrides{}) + block, err := s.simulateWithTracer(transactions.WrapSignedTxnsWithAD(txgroup), mockTracer, ResultEvalOverrides{}) require.NoError(t, err) evalBlock := block.Block() diff --git a/ledger/simulation/trace.go b/ledger/simulation/trace.go index 09d51f3193..a1cb311089 100644 --- a/ledger/simulation/trace.go +++ b/ledger/simulation/trace.go @@ -45,6 +45,10 @@ type TxnResult struct { // // In that case, it will be populated with the unnamed resources accessed by this transaction. UnnamedResourcesAccessed *ResourceTracker + + // If the signer needed to be changed, this will be the address of the required signer + // This will only be present if FixSigners is true in the EvalOverrides + FixedSigner basics.Address } // TxnGroupResult contains the simulation result for a single transaction group @@ -90,6 +94,7 @@ type ResultEvalOverrides struct { MaxLogCalls *uint64 MaxLogSize *uint64 ExtraOpcodeBudget uint64 + FixSigners bool } // LogBytesLimit hardcode limit of how much bytes one can log per transaction during simulation (with AllowMoreLogging) @@ -206,6 +211,7 @@ func makeSimulationResult(lastRound basics.Round, request Request, developerAPI AllowEmptySignatures: request.AllowEmptySignatures, ExtraOpcodeBudget: request.ExtraOpcodeBudget, AllowUnnamedResources: request.AllowUnnamedResources, + FixSigners: request.FixSigners, }.AllowMoreLogging(request.AllowMoreLogging) if err := validateSimulateRequest(request, developerAPI); err != nil { diff --git a/ledger/simulation/tracer.go b/ledger/simulation/tracer.go index b867cf6aac..94fa1394f7 100644 --- a/ledger/simulation/tracer.go +++ b/ledger/simulation/tracer.go @@ -98,14 +98,16 @@ type evalTracer struct { // scratchSlots are the scratch slots changed on current opcode (currently either `store` or `stores`). // NOTE: this field scratchSlots is used only for scratch change exposure. scratchSlots []uint64 + + groups [][]transactions.SignedTxnWithAD } -func makeEvalTracer(lastRound basics.Round, request Request, developerAPI bool) (*evalTracer, error) { +func makeEvalTracer(lastRound basics.Round, group []transactions.SignedTxnWithAD, request Request, developerAPI bool) (*evalTracer, error) { result, err := makeSimulationResult(lastRound, request, developerAPI) if err != nil { return nil, err } - return &evalTracer{result: &result}, nil + return &evalTracer{result: &result, groups: [][]transactions.SignedTxnWithAD{group}}, nil } // handleError is responsible for setting the failedAt field properly. @@ -512,4 +514,44 @@ func (tracer *evalTracer) AfterProgram(cx *logic.EvalContext, pass bool, evalErr } else { tracer.handleError(evalError) } + + // Since an app could rekey multiple accounts, we need to go over the + // rest of the txngroup and make sure all the auth addrs are correct + if tracer.result.EvalOverrides.FixSigners && len(tracer.relativeCursor) == 1 { + knownAuthAddrs := make(map[basics.Address]basics.Address) + // iterate over all txns in the group after this one + for i := groupIndex + 1; i < len(cx.TxnGroup); i++ { + stxn := &tracer.groups[0][i] + sender := stxn.Txn.Sender + + // If we don't already know the auth addr, get it from the ledger + if _, authAddrKnown := knownAuthAddrs[sender]; !authAddrKnown { + // Get the auth addr from the ledger + data, err := cx.Ledger.AccountData(sender) + if err != nil { + panic(err) + } + + knownAuthAddrs[sender] = data.AuthAddr + } + + // Fix the current auth addr if this txn doesn't have a signature + if txnHasNoSignature(stxn.SignedTxn) { + stxn.AuthAddr = knownAuthAddrs[sender] + if stxn.AuthAddr == sender { + stxn.AuthAddr = basics.Address{} + } + } + + // If this is an appl, we can break since we know AfterProgram will be called afterwards + if stxn.Txn.Type == protocol.ApplicationCallTx { + break + } + + // If this is a rekey, save the auth addr for the sender + if stxn.Txn.RekeyTo != (basics.Address{}) { + knownAuthAddrs[sender] = stxn.Txn.RekeyTo + } + } + } } diff --git a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go index a30ce06b4f..66601c1737 100644 --- a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go +++ b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go @@ -2695,3 +2695,86 @@ int 1 } a.Equal(expectedResult, resp) } + +func TestSimulateWithFixSigners(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) + + rekeyTxn, err := testClient.ConstructPayment(senderAddress, senderAddress, 0, 1, nil, "", [32]byte{}, 0, 0) + a.NoError(err) + + var authAddr basics.Address + crypto.RandBytes(authAddr[:]) + rekeyTxn.RekeyTo = authAddr + + txn, err := testClient.ConstructPayment(senderAddress, senderAddress, 0, 1, nil, "", [32]byte{}, 0, 0) + a.NoError(err) + + gid, err := testClient.GroupID([]transactions.Transaction{rekeyTxn, txn}) + a.NoError(err) + + rekeyTxn.Group = gid + txn.Group = gid + + simulateRequest := v2.PreEncodedSimulateRequest{ + TxnGroups: []v2.PreEncodedSimulateRequestTransactionGroup{ + { + Txns: []transactions.SignedTxn{{Txn: rekeyTxn}, {Txn: txn}}, + }, + }, + AllowEmptySignatures: true, + FixSigners: true, + } + result, err := testClient.SimulateTransactions(simulateRequest) + a.NoError(err) + + allowEmptySignatures := true + fixSigners := true + authAddrStr := authAddr.String() + expectedResult := v2.PreEncodedSimulateResponse{ + Version: 2, + LastRound: result.LastRound, + TxnGroups: []v2.PreEncodedSimulateTxnGroupResult{ + { + Txns: []v2.PreEncodedSimulateTxnResult{ + { + Txn: v2.PreEncodedTxInfo{ + Txn: transactions.SignedTxn{Txn: rekeyTxn}, + }, + }, + { + Txn: v2.PreEncodedTxInfo{ + Txn: transactions.SignedTxn{Txn: txn}, + }, + FixedSigner: &authAddrStr, + }, + }, + }, + }, + EvalOverrides: &model.SimulationEvalOverrides{ + AllowEmptySignatures: &allowEmptySignatures, + FixSigners: &fixSigners, + }, + } + a.Equal(expectedResult, result) +} From 02961e3adf8f6de7941d27ce4c073ad8eb8d89b8 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Mon, 17 Jun 2024 15:20:48 +0000 Subject: [PATCH 154/218] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index 573541ac97..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/config/version.go b/config/version.go index 8716fbcdcb..5a1b31ce0e 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 25 +const VersionMinor = 26 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From 052f832ac13402c6f3b8dc3760970c1a4ced9e1b Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 20 Jun 2024 17:12:48 -0400 Subject: [PATCH 155/218] catchup: do not loop forever if there is no peers (#6037) * Add context cancellation check to fetchRound peers retrieval loop * This prevented some e2e tests to finish when a other nodes quit but the last node fell into catchup mode --- catchup/service.go | 7 +++++++ .../features/catchup/basicCatchup_test.go | 17 ----------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/catchup/service.go b/catchup/service.go index b1720c4fce..b00a787ee3 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -760,6 +760,13 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy psp, getPeerErr := ps.getNextPeer() if getPeerErr != nil { s.log.Debugf("fetchRound: was unable to obtain a peer to retrieve the block from") + select { + case <-s.ctx.Done(): + logging.Base().Debugf("fetchRound was asked to quit while collecting peers") + return + default: + } + s.net.RequestConnectOutgoing(true, s.ctx.Done()) continue } diff --git a/test/e2e-go/features/catchup/basicCatchup_test.go b/test/e2e-go/features/catchup/basicCatchup_test.go index 938313206d..2e3ac87943 100644 --- a/test/e2e-go/features/catchup/basicCatchup_test.go +++ b/test/e2e-go/features/catchup/basicCatchup_test.go @@ -128,15 +128,6 @@ func runCatchupOverGossip(t fixtures.TestingTB, a.NoError(err) a.Empty(cfg.NetworkProtocolVersion) cfg.NetworkProtocolVersion = ledgerNodeDowngradeTo - cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test - cfg.SaveToDisk(dir) - } else { - // TODO: remove when TestCatchupOverGossip is fixed - dir, err := fixture.GetNodeDir("Node") - a.NoError(err) - cfg, err := config.LoadConfigFromDisk(dir) - a.NoError(err) - cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test cfg.SaveToDisk(dir) } @@ -147,14 +138,6 @@ func runCatchupOverGossip(t fixtures.TestingTB, a.NoError(err) a.Empty(cfg.NetworkProtocolVersion) cfg.NetworkProtocolVersion = fetcherNodeDowngradeTo - cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test - cfg.SaveToDisk(dir) - } else { - // TODO: remove when TestCatchupOverGossip is fixed - dir := fixture.PrimaryDataDir() - cfg, err := config.LoadConfigFromDisk(dir) - a.NoError(err) - cfg.BaseLoggerDebugLevel = 5 // debug logging while debugging this test cfg.SaveToDisk(dir) } From 052ceb2d9f81b131eae0ee0ee57e2c41d2d731a2 Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 21 Jun 2024 14:17:38 -0400 Subject: [PATCH 156/218] CICD: remove mac AMD64 support from circleci (#6036) --- .circleci/config.yml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8208fe063b..f431e91160 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,30 +45,18 @@ executors: machine: image: << pipeline.parameters.ubuntu_image >> resource_class: arm.large - mac_amd64_medium: + mac_arm64_medium: macos: xcode: 14.2.0 - resource_class: macos.x86.medium.gen2 + resource_class: macos.m1.medium.gen1 environment: HOMEBREW_NO_AUTO_UPDATE: "true" - mac_amd64_large: + mac_arm64_large: macos: xcode: 14.2.0 - # Since they removed the large class for amd64, we will use medium here too. - resource_class: macos.x86.medium.gen2 - environment: - HOMEBREW_NO_AUTO_UPDATE: "true" - mac_arm64: &executor-mac-arm64 - machine: true - resource_class: algorand/macstadium-m1 + resource_class: macos.m1.large.gen1 environment: HOMEBREW_NO_AUTO_UPDATE: "true" - # these are required b/c jobs explicitly assign sizes to the executors - # for `mac_arm64` there is only one size - mac_arm64_medium: - <<: *executor-mac-arm64 - mac_arm64_large: - <<: *executor-mac-arm64 slack-fail-stop-step: &slack-fail-post-step post-steps: @@ -86,7 +74,7 @@ workflows: name: << matrix.platform >>_build_nightly matrix: &matrix-nightly parameters: - platform: ["amd64", "arm64", "mac_amd64", "mac_arm64"] + platform: ["amd64", "arm64", "mac_arm64"] filters: &filters-nightly branches: only: @@ -137,7 +125,7 @@ workflows: name: << matrix.platform >>_<< matrix.job_type >>_verification matrix: parameters: - platform: ["amd64", "arm64", "mac_amd64", "mac_arm64"] + platform: ["amd64", "arm64", "mac_arm64"] job_type: ["test_nightly", "integration_nightly", "e2e_expect_nightly"] requires: - << matrix.platform >>_<< matrix.job_type >> From 24382d85ad35d5a2f200434936fd643a909b6eab Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:05:27 -0400 Subject: [PATCH 157/218] node: close ledger and part keys on node shutdown (#6039) --- data/pools/transactionPool.go | 29 +++++++++++++++++++++++++++++ ledger/notifier.go | 2 ++ node/node.go | 18 ++++++++++++++---- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index 687a3db80c..afe12f2363 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -95,6 +95,11 @@ type TransactionPool struct { // stateproofOverflowed indicates that a stateproof transaction was allowed to // exceed the txPoolMaxSize. This flag is reset to false OnNewBlock stateproofOverflowed bool + + // shutdown is set to true when the pool is being shut down. It is checked in exported methods + // to prevent pool operations like remember and recomputing the block evaluator + // from using down stream resources like ledger that may be shutting down. + shutdown bool } // BlockEvaluator defines the block evaluator interface exposed by the ledger package. @@ -113,6 +118,8 @@ type VotingAccountSupplier interface { VotingAccountsForRound(basics.Round) []basics.Address } +var errPoolShutdown = errors.New("transaction pool is shutting down") + // MakeTransactionPool makes a transaction pool. func MakeTransactionPool(ledger *ledger.Ledger, cfg config.Local, log logging.Logger, vac VotingAccountSupplier) *TransactionPool { if cfg.TxPoolExponentialIncreaseFactor < 1 { @@ -430,6 +437,10 @@ func (pool *TransactionPool) ingest(txgroup []transactions.SignedTxn, params poo return ErrNoPendingBlockEvaluator } + if pool.shutdown { + return errPoolShutdown + } + if !params.recomputing { // Make sure that the latest block has been processed by OnNewBlock(). // If not, we might be in a race, so wait a little bit for OnNewBlock() @@ -441,6 +452,10 @@ func (pool *TransactionPool) ingest(txgroup []transactions.SignedTxn, params poo if pool.pendingBlockEvaluator == nil { return ErrNoPendingBlockEvaluator } + // recheck if the pool is shutting down since TimedWait above releases the lock + if pool.shutdown { + return errPoolShutdown + } } err := pool.checkSufficientFee(txgroup) @@ -529,6 +544,10 @@ func (pool *TransactionPool) OnNewBlock(block bookkeeping.Block, delta ledgercor pool.mu.Lock() defer pool.mu.Unlock() + if pool.shutdown { + return + } + defer pool.cond.Broadcast() if pool.pendingBlockEvaluator == nil || block.Round() >= pool.pendingBlockEvaluator.Round() { // Adjust the pool fee threshold. The rules are: @@ -1010,3 +1029,13 @@ func (pool *TransactionPool) AssembleDevModeBlock() (assembled *ledgercore.Unfin assembled, err = pool.AssembleBlock(pool.pendingBlockEvaluator.Round(), time.Now().Add(pool.proposalAssemblyTime)) return } + +// Shutdown stops the transaction pool from accepting new transactions and blocks. +// It takes the pool.mu lock in order to ensure there is no pending remember or block operations in flight +// and sets the shutdown flag to true. +func (pool *TransactionPool) Shutdown() { + pool.mu.Lock() + defer pool.mu.Unlock() + + pool.shutdown = true +} diff --git a/ledger/notifier.go b/ledger/notifier.go index aabf62d080..f97e1c77e6 100644 --- a/ledger/notifier.go +++ b/ledger/notifier.go @@ -74,6 +74,8 @@ func (bn *blockNotifier) worker() { func (bn *blockNotifier) close() { bn.mu.Lock() + bn.pendingBlocks = nil + bn.listeners = nil if bn.running { bn.running = false bn.cond.Broadcast() diff --git a/node/node.go b/node/node.go index d1c6cc4b82..6c77b4fbc0 100644 --- a/node/node.go +++ b/node/node.go @@ -152,6 +152,7 @@ type AlgorandFullNode struct { tracer messagetracer.MessageTracer stateProofWorker *stateproof.Worker + partHandles []db.Accessor } // TxnWithStatus represents information about a single transaction, @@ -418,6 +419,12 @@ func (node *AlgorandFullNode) Stop() { defer func() { node.mu.Unlock() node.waitMonitoringRoutines() + + // oldKeyDeletionThread uses accountManager registry so must be stopped before accountManager is closed + node.accountManager.Registry().Close() + for h := range node.partHandles { + node.partHandles[h].Close() + } }() node.net.ClearHandlers() @@ -430,6 +437,7 @@ func (node *AlgorandFullNode) Stop() { node.stateProofWorker.Stop() node.txHandler.Stop() node.agreementService.Shutdown() + node.agreementService.Accessor.Close() node.catchupService.Stop() node.txPoolSyncerService.Stop() node.blockService.Stop() @@ -441,7 +449,9 @@ func (node *AlgorandFullNode) Stop() { node.lowPriorityCryptoVerificationPool.Shutdown() node.cryptoPool.Shutdown() node.log.Debug("crypto worker pools have stopped") + node.transactionPool.Shutdown() node.cancelCtx() + node.ledger.Close() } // note: unlike the other two functions, this accepts a whole filename @@ -987,12 +997,12 @@ func (node *AlgorandFullNode) loadParticipationKeys() error { // These files are not ephemeral and must be deleted eventually since // this function is called to load files located in the node on startup added := node.accountManager.AddParticipation(part, false) - if added { - node.log.Infof("Loaded participation keys from storage: %s %s", part.Address(), info.Name()) - } else { + if !added { part.Close() continue } + node.log.Infof("Loaded participation keys from storage: %s %s", part.Address(), info.Name()) + node.partHandles = append(node.partHandles, handle) err = insertStateProofToRegistry(part, node) if err != nil { return err @@ -1024,7 +1034,7 @@ func (node *AlgorandFullNode) txPoolGaugeThread(done <-chan struct{}) { defer node.monitoringRoutinesWaitGroup.Done() ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() - for true { + for { select { case <-ticker.C: txPoolGauge.Set(uint64(node.transactionPool.PendingCount())) From c99a1c91cc7c11449b77557a1d81e669c92ca87b Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:36:48 -0400 Subject: [PATCH 158/218] build: add universal Mac make target (#6023) --- Makefile | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8301771718..7ad7f219f8 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,13 @@ endif SRCPATH := $(shell pwd) ARCH := $(shell ./scripts/archtype.sh) OS_TYPE := $(shell ./scripts/ostype.sh) +# overrides for cross-compiling platform-specific binaries +ifdef CROSS_COMPILE_ARCH + ARCH := $(CROSS_COMPILE_ARCH) + GO_INSTALL := CGO_ENABLED=1 GOOS=$(OS_TYPE) GOARCH=$(ARCH) go build -o $(GOPATH1)/bin-$(OS_TYPE)-$(ARCH) +else + GO_INSTALL := go install +endif S3_RELEASE_BUCKET = $$S3_RELEASE_BUCKET GOLANG_VERSIONS := $(shell ./scripts/get_golang_version.sh all) @@ -153,10 +160,35 @@ crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a: cp -R crypto/libsodium-fork/. crypto/copies/$(OS_TYPE)/$(ARCH)/libsodium-fork cd crypto/copies/$(OS_TYPE)/$(ARCH)/libsodium-fork && \ ./autogen.sh --prefix $(SRCPATH)/crypto/libs/$(OS_TYPE)/$(ARCH) && \ - ./configure --disable-shared --prefix="$(SRCPATH)/crypto/libs/$(OS_TYPE)/$(ARCH)" && \ + ./configure --disable-shared --prefix="$(SRCPATH)/crypto/libs/$(OS_TYPE)/$(ARCH)" $(EXTRA_CONFIGURE_FLAGS) && \ $(MAKE) && \ $(MAKE) install +universal: +ifeq ($(OS_TYPE),darwin) + # build amd64 Mac binaries + mkdir -p $(GOPATH1)/bin-darwin-amd64 + CROSS_COMPILE_ARCH=amd64 GOBIN=$(GOPATH1)/bin-darwin-amd64 MACOSX_DEPLOYMENT_TARGET=12.0 EXTRA_CONFIGURE_FLAGS='CFLAGS="-arch x86_64 -mmacos-version-min=12.0" --host=x86_64-apple-darwin' $(MAKE) + + # build arm64 Mac binaries + mkdir -p $(GOPATH1)/bin-darwin-arm64 + CROSS_COMPILE_ARCH=arm64 GOBIN=$(GOPATH1)/bin-darwin-arm64 MACOSX_DEPLOYMENT_TARGET=12.0 EXTRA_CONFIGURE_FLAGS='CFLAGS="-arch arm64 -mmacos-version-min=12.0" --host=aarch64-apple-darwin' $(MAKE) + + # lipo together + mkdir -p $(GOPATH1)/bin-darwin-universal + for binary in $$(ls $(GOPATH1)/bin-darwin-arm64); do \ + if [ -f $(GOPATH1)/bin-darwin-amd64/$$binary ]; then \ + lipo -create -output $(GOPATH1)/bin-darwin-universal/$$binary \ + $(GOPATH1)/bin-darwin-arm64/$$binary \ + $(GOPATH1)/bin-darwin-amd64/$$binary; \ + else \ + echo "Warning: Binary $$binary exists in arm64 but not in amd64"; \ + fi \ + done +else + $(error OS_TYPE must be darwin for universal builds) +endif + deps: ./scripts/check_deps.sh @@ -212,11 +244,11 @@ ${GOCACHE}/file.txt: touch "${GOCACHE}"/file.txt buildsrc: check-go-version crypto/libs/$(OS_TYPE)/$(ARCH)/lib/libsodium.a node_exporter NONGO_BIN ${GOCACHE}/file.txt - go install $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... + $(GO_INSTALL) $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... buildsrc-special: cd tools/block-generator && \ - go install $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... + $(GO_INSTALL) $(GOTRIMPATH) $(GOTAGS) $(GOBUILDMODE) -ldflags="$(GOLDFLAGS)" ./... check-go-version: ./scripts/check_golang_version.sh build @@ -331,7 +363,7 @@ dump: $(addprefix gen/,$(addsuffix /genesis.dump, $(NETWORKS))) install: build scripts/dev_install.sh -p $(GOPATH1)/bin -.PHONY: default fmt lint check_shell sanity cover prof deps build test fulltest shorttest clean cleango deploy node_exporter install %gen gen NONGO_BIN check-go-version rebuild_kmd_swagger +.PHONY: default fmt lint check_shell sanity cover prof deps build test fulltest shorttest clean cleango deploy node_exporter install %gen gen NONGO_BIN check-go-version rebuild_kmd_swagger universal ###### TARGETS FOR CICD PROCESS ###### include ./scripts/release/mule/Makefile.mule From f523300433d101b45b19ae119d1dec95008a6a96 Mon Sep 17 00:00:00 2001 From: Henrik Soerensen Date: Wed, 26 Jun 2024 16:19:29 -0400 Subject: [PATCH 159/218] Metrics: Add algod version to metrics (#6003) Co-authored-by: cce <51567+cce@users.noreply.github.com> --- daemon/algod/server.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 12cbf3e968..13432957c3 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -28,6 +28,7 @@ import ( "os" "os/signal" "path/filepath" + "runtime" "strings" "syscall" "time" @@ -230,6 +231,16 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes NodeExporterPath: cfg.NodeExporterPath, }) + var currentVersion = config.GetCurrentVersion() + var algodBuildInfoGauge = metrics.MakeGauge(metrics.MetricName{Name: "algod_build_info", Description: "Algod build info"}) + algodBuildInfoGauge.SetLabels(1, map[string]string{ + "version": currentVersion.String(), + "goarch": runtime.GOARCH, + "goos": runtime.GOOS, + "commit": currentVersion.CommitHash, + "channel": currentVersion.Channel, + }) + var serverNode ServerNode if cfg.EnableFollowMode { var followerNode *node.AlgorandFollowerNode From d3831cdf780ee1fc05c4a5860699a70671b8f265 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:31:00 -0400 Subject: [PATCH 160/218] ledger: restore block listeners on reloadLedger (#6041) --- catchup/fetcher_test.go | 3 +- catchup/pref_test.go | 4 +-- daemon/algod/api/server/v2/test/helpers.go | 2 +- data/common_test.go | 2 +- data/datatest/fabricateLedger.go | 2 +- data/ledger.go | 3 +- data/txHandler_test.go | 18 +++++------ ledger/ledger.go | 9 ++++++ ledger/ledger_test.go | 35 ++++++++++++++++++++++ node/assemble_test.go | 4 +-- node/follower_node.go | 8 ++--- node/node.go | 10 ++----- node/node_test.go | 2 +- rpcs/blockService_test.go | 2 +- 14 files changed, 69 insertions(+), 35 deletions(-) diff --git a/catchup/fetcher_test.go b/catchup/fetcher_test.go index dab8da4688..e219852bf4 100644 --- a/catchup/fetcher_test.go +++ b/catchup/fetcher_test.go @@ -66,8 +66,7 @@ func buildTestLedger(t *testing.T, blk bookkeeping.Block) (ledger *data.Ledger, cfg := config.GetDefaultLocal() cfg.Archival = true ledger, err = data.LoadLedger( - log, t.Name(), inMem, protocol.ConsensusCurrentVersion, genBal, "", genHash, - nil, cfg, + log, t.Name(), inMem, protocol.ConsensusCurrentVersion, genBal, "", genHash, cfg, ) if err != nil { t.Fatal("couldn't build ledger", err) diff --git a/catchup/pref_test.go b/catchup/pref_test.go index 38b2a9d16e..a72ed855ec 100644 --- a/catchup/pref_test.go +++ b/catchup/pref_test.go @@ -62,7 +62,7 @@ func BenchmarkServiceFetchBlocks(b *testing.B) { for i := 0; i < b.N; i++ { inMem := true prefix := b.Name() + "empty" + strconv.Itoa(i) - local, err := data.LoadLedger(logging.TestingLog(b), prefix, inMem, protocol.ConsensusCurrentVersion, genesisBalances, "", crypto.Digest{}, nil, cfg) + local, err := data.LoadLedger(logging.TestingLog(b), prefix, inMem, protocol.ConsensusCurrentVersion, genesisBalances, "", crypto.Digest{}, cfg) require.NoError(b, err) // Make Service @@ -150,7 +150,7 @@ func benchenv(t testing.TB, numAccounts, numBlocks int) (ledger, emptyLedger *da cfg := config.GetDefaultLocal() cfg.Archival = true prefix := t.Name() + "empty" - emptyLedger, err = data.LoadLedger(logging.TestingLog(t), prefix, inMem, protocol.ConsensusCurrentVersion, genesisBalances, "", crypto.Digest{}, nil, cfg) + emptyLedger, err = data.LoadLedger(logging.TestingLog(t), prefix, inMem, protocol.ConsensusCurrentVersion, genesisBalances, "", crypto.Digest{}, cfg) require.NoError(t, err) ledger, err = datatest.FabricateLedger(logging.TestingLog(t), t.Name(), parts, genesisBalances, emptyLedger.LastRound()+basics.Round(numBlocks)) diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index ad028fc8ae..5ba5256d63 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -313,7 +313,7 @@ func testingenvWithBalances(t testing.TB, minMoneyAtStart, maxMoneyAtStart, numA const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := data.LoadLedger(logging.Base(), t.Name(), inMem, protocol.ConsensusFuture, bootstrap, genesisID, genesisHash, nil, cfg) + ledger, err := data.LoadLedger(logging.Base(), t.Name(), inMem, protocol.ConsensusFuture, bootstrap, genesisID, genesisHash, cfg) if err != nil { panic(err) } diff --git a/data/common_test.go b/data/common_test.go index 6079f4a226..6f947067fa 100644 --- a/data/common_test.go +++ b/data/common_test.go @@ -121,7 +121,7 @@ func testingenv(t testing.TB, numAccounts, numTxs int, offlineAccounts bool) (*L const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := LoadLedger(logging.Base(), t.Name(), inMem, protocol.ConsensusCurrentVersion, bootstrap, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(logging.Base(), t.Name(), inMem, protocol.ConsensusCurrentVersion, bootstrap, genesisID, genesisHash, cfg) if err != nil { panic(err) } diff --git a/data/datatest/fabricateLedger.go b/data/datatest/fabricateLedger.go index 9ad7bed4fc..cfde34af8a 100644 --- a/data/datatest/fabricateLedger.go +++ b/data/datatest/fabricateLedger.go @@ -38,7 +38,7 @@ func FabricateLedger(log logging.Logger, ledgerName string, accounts []account.P const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := data.LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genesis, "", crypto.Digest{}, nil, cfg) + ledger, err := data.LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genesis, "", crypto.Digest{}, cfg) if err != nil { return nil, err } diff --git a/data/ledger.go b/data/ledger.go index fa3b958373..579d324d0b 100644 --- a/data/ledger.go +++ b/data/ledger.go @@ -81,7 +81,7 @@ type roundSeed struct { func LoadLedger[T string | ledger.DirsAndPrefix]( log logging.Logger, dir T, memory bool, genesisProto protocol.ConsensusVersion, genesisBal bookkeeping.GenesisBalances, genesisID string, genesisHash crypto.Digest, - blockListeners []ledgercore.BlockListener, cfg config.Local, + cfg config.Local, ) (*Ledger, error) { if genesisBal.Balances == nil { genesisBal.Balances = make(map[basics.Address]basics.AccountData) @@ -115,7 +115,6 @@ func LoadLedger[T string | ledger.DirsAndPrefix]( } l.Ledger = ll - l.RegisterBlockListeners(blockListeners) return l, nil } diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 896fbb161d..d395779f33 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -109,7 +109,7 @@ func BenchmarkTxHandlerProcessing(b *testing.B) { cfg.Archival = true cfg.TxBacklogReservedCapacityPerPeer = 1 cfg.IncomingConnectionsLimit = 10 - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(b, err) defer ledger.Close() @@ -1027,7 +1027,7 @@ func TestTxHandlerProcessIncomingCacheTxPoolDrop(t *testing.T) { cfg.Archival = true cfg.EnableTxBacklogRateLimiting = false cfg.TxIncomingFilteringFlags = 3 // txFilterRawMsg + txFilterCanonical - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) defer ledger.Close() @@ -1196,7 +1196,7 @@ func incomingTxHandlerProcessing(maxGroupSize, numberOfTransactionGroups int, t cfg := config.GetDefaultLocal() cfg.Archival = true cfg.EnableTxBacklogRateLimiting = false - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) defer ledger.Close() @@ -1641,7 +1641,7 @@ func (g *txGenerator) makeLedger(tb testing.TB, cfg config.Local, log logging.Lo ledgerName := fmt.Sprintf("%s-in_mem-w_inv=%d", namePrefix, ivrString) ledgerName = strings.Replace(ledgerName, "#", "-", 1) const inMem = true - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(tb, err) return ledger } @@ -2183,7 +2183,7 @@ func TestTxHandlerRememberReportErrorsWithTxPool(t *testing.T) { //nolint:parall cfg := config.GetDefaultLocal() cfg.Archival = true cfg.TxPoolSize = config.MaxTxGroupSize + 1 - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) defer ledger.Close() @@ -2419,7 +2419,7 @@ func TestTxHandlerRestartWithBacklogAndTxPool(t *testing.T) { //nolint:parallelt const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) defer ledger.Ledger.Close() @@ -2524,7 +2524,7 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { cfg.TxBacklogServiceRateWindowSeconds = 1 cfg.TxBacklogAppTxPerSecondRate = 3 cfg.TxBacklogSize = 3 - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, bookkeeping.GenesisBalances{}, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, bookkeeping.GenesisBalances{}, genesisID, genesisHash, cfg) require.NoError(t, err) defer ledger.Close() @@ -2636,7 +2636,7 @@ func TestTxHandlerAppRateLimiter(t *testing.T) { cfg.TxBacklogAppTxRateLimiterMaxSize = 100 cfg.TxBacklogServiceRateWindowSeconds = 1 cfg.TxBacklogAppTxPerSecondRate = 3 - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) defer ledger.Close() @@ -2705,7 +2705,7 @@ func TestTxHandlerCapGuard(t *testing.T) { cfg.IncomingConnectionsLimit = 1 cfg.TxBacklogSize = 3 - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) defer ledger.Close() diff --git a/ledger/ledger.go b/ledger/ledger.go index 2cc1b36ee1..7459c23037 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -210,6 +210,10 @@ func (l *Ledger) reloadLedger() error { l.trackerMu.Lock() defer l.trackerMu.Unlock() + // save block listeners to recover them later + blockListeners := make([]ledgercore.BlockListener, 0, len(l.notifier.listeners)) + blockListeners = append(blockListeners, l.notifier.listeners...) + // close the trackers. l.trackers.close() @@ -256,6 +260,9 @@ func (l *Ledger) reloadLedger() error { return err } + // restore block listeners since l.notifier might not survive a reload + l.notifier.register(blockListeners) + // post-init actions if trackerDBInitParams.VacuumOnStartup || l.cfg.OptimizeAccountsDatabaseOnStartup { err = l.accts.vacuumDatabase(context.Background()) @@ -423,6 +430,8 @@ func (l *Ledger) Close() { // RegisterBlockListeners registers listeners that will be called when a // new block is added to the ledger. func (l *Ledger) RegisterBlockListeners(listeners []ledgercore.BlockListener) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() l.notifier.register(listeners) } diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 2a9666688b..968e6d8b21 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -3422,5 +3422,40 @@ func TestLedgerRetainMinOffCatchpointInterval(t *testing.T) { } }() } +} + +type testBlockListener struct { + id int +} + +func (t *testBlockListener) OnNewBlock(bookkeeping.Block, ledgercore.StateDelta) {} + +// TestLedgerRegisterBlockListeners ensures that the block listeners survive reloadLedger +func TestLedgerRegisterBlockListeners(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, _, _ := ledgertesting.NewTestGenesis() + var genHash crypto.Digest + crypto.RandBytes(genHash[:]) + cfg := config.GetDefaultLocal() + l := newSimpleLedgerFull(t, genBalances, protocol.ConsensusCurrentVersion, genHash, cfg) + defer l.Close() + l.RegisterBlockListeners([]ledgercore.BlockListener{&testBlockListener{1}, &testBlockListener{2}}) + l.RegisterBlockListeners([]ledgercore.BlockListener{&testBlockListener{3}}) + + require.Equal(t, 3, len(l.notifier.listeners)) + var ids []int + for _, bl := range l.notifier.listeners { + ids = append(ids, bl.(*testBlockListener).id) + } + require.Equal(t, []int{1, 2, 3}, ids) + + l.reloadLedger() + + ids = nil + for _, bl := range l.notifier.listeners { + ids = append(ids, bl.(*testBlockListener).id) + } + require.Equal(t, []int{1, 2, 3}, ids) } diff --git a/node/assemble_test.go b/node/assemble_test.go index 51ff7d8edc..d2bf4dd7f7 100644 --- a/node/assemble_test.go +++ b/node/assemble_test.go @@ -83,7 +83,7 @@ func BenchmarkAssembleBlock(b *testing.B) { const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := data.LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := data.LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(b, err) l := ledger @@ -212,7 +212,7 @@ func TestAssembleBlockTransactionPoolBehind(t *testing.T) { const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = true - ledger, err := data.LoadLedger(log, "ledgerName", inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, nil, cfg) + ledger, err := data.LoadLedger(log, "ledgerName", inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) l := ledger diff --git a/node/follower_node.go b/node/follower_node.go index 8483f14679..e475b25481 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -116,17 +116,13 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo 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) + node.ledger, err = data.LoadLedger(node.log, ledgerPaths, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, cfg) if err != nil { log.Errorf("Cannot initialize ledger (%v): %v", ledgerPaths, err) return nil, err } - blockListeners := []ledgercore.BlockListener{ - node, - } - - node.ledger.RegisterBlockListeners(blockListeners) + node.ledger.RegisterBlockListeners([]ledgercore.BlockListener{node}) if cfg.IsGossipServer() { rpcs.MakeHealthService(node.net) diff --git a/node/node.go b/node/node.go index 6c77b4fbc0..58160ff3e0 100644 --- a/node/node.go +++ b/node/node.go @@ -223,7 +223,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd 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) + node.ledger, err = data.LoadLedger(node.log, ledgerPaths, false, genesis.Proto, genalloc, node.genesisID, node.genesisHash, cfg) if err != nil { log.Errorf("Cannot initialize ledger (%v): %v", ledgerPaths, err) return nil, err @@ -246,12 +246,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd node.transactionPool = pools.MakeTransactionPool(node.ledger.Ledger, cfg, node.log, node) - blockListeners := []ledgercore.BlockListener{ - node.transactionPool, - node, - } - - node.ledger.RegisterBlockListeners(blockListeners) + node.ledger.RegisterBlockListeners([]ledgercore.BlockListener{node.transactionPool, node}) txHandlerOpts := data.TxHandlerOpts{ TxPool: node.transactionPool, ExecutionPool: node.lowPriorityCryptoVerificationPool, @@ -1211,6 +1206,7 @@ func (node *AlgorandFullNode) SetCatchpointCatchupMode(catchpointCatchupMode boo return } defer node.mu.Unlock() + // start node.transactionPool.Reset() node.catchupService.Start() diff --git a/node/node_test.go b/node/node_test.go index dabb7958a5..54f2e1e6cc 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -168,7 +168,7 @@ func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, verificationP cfg, err := config.LoadConfigFromDisk(rootDirectory) require.NoError(t, err) cfg.Archival = true - _, err = data.LoadLedger(logging.Base().With("name", nodeID), ledgerFilenamePrefix, inMem, g.Proto, bootstrap, g.ID(), g.Hash(), nil, cfg) + _, err = data.LoadLedger(logging.Base().With("name", nodeID), ledgerFilenamePrefix, inMem, g.Proto, bootstrap, g.ID(), g.Hash(), cfg) require.NoError(t, err) } diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index 3aab7c4abb..7b1f756f08 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -520,7 +520,7 @@ func makeLedger(t *testing.T, namePostfix string) *data.Ledger { prefix := t.Name() + namePostfix ledger, err := data.LoadLedger( log, prefix, inMem, protocol.ConsensusCurrentVersion, genBal, "", genHash, - nil, cfg, + cfg, ) require.NoError(t, err) return ledger From 63c0d5bd60fbe50b17788ed619f089d0288b214d Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:54:11 -0400 Subject: [PATCH 161/218] testing: set pprof labels for goroutines that use the same code for different cases (#4350) --- agreement/demux.go | 2 ++ network/p2pNetwork.go | 4 ++-- network/wsNetwork.go | 10 ++++++---- node/node.go | 7 ++++--- util/execpool/backlog.go | 11 +++++++---- util/execpool/pool.go | 11 +++++++---- util/process.go | 7 +++++++ 7 files changed, 35 insertions(+), 17 deletions(-) diff --git a/agreement/demux.go b/agreement/demux.go index 33e15e6cfd..2f0e9b269b 100644 --- a/agreement/demux.go +++ b/agreement/demux.go @@ -25,6 +25,7 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/logspec" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util" ) const ( @@ -113,6 +114,7 @@ func (d *demux) tokenizeMessages(ctx context.Context, net Network, tag protocol. defer func() { close(decoded) }() + util.SetGoroutineLabels("tokenizeTag", string(tag)) for { select { case raw, ok := <-networkMessages: diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index c5422af849..6301b1b521 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -138,11 +138,11 @@ func (n *P2PNetwork) Start() { for i := 0; i < incomingThreads; i++ { n.wg.Add(1) // We pass the peersConnectivityCheckTicker.C here so that we don't need to syncronize the access to the ticker's data structure. - go n.handler.messageHandlerThread(&n.wg, n.wsPeersConnectivityCheckTicker.C, n) + go n.handler.messageHandlerThread(&n.wg, n.wsPeersConnectivityCheckTicker.C, n, "network", "P2PNetwork") } n.wg.Add(1) - go n.broadcaster.broadcastThread(&n.wg, n) + go n.broadcaster.broadcastThread(&n.wg, n, "network", "P2PNetwork") n.service.DialPeersUntilTargetCount(n.config.GossipFanout) n.wg.Add(1) diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 6fc97def5e..4a491f4f9f 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -747,10 +747,10 @@ func (wn *WebsocketNetwork) Start() { for i := 0; i < incomingThreads; i++ { wn.wg.Add(1) // We pass the peersConnectivityCheckTicker.C here so that we don't need to syncronize the access to the ticker's data structure. - go wn.handler.messageHandlerThread(&wn.wg, wn.peersConnectivityCheckTicker.C, wn) + go wn.handler.messageHandlerThread(&wn.wg, wn.peersConnectivityCheckTicker.C, wn, "network", "WebsocketNetwork") } wn.wg.Add(1) - go wn.broadcaster.broadcastThread(&wn.wg, wn) + go wn.broadcaster.broadcastThread(&wn.wg, wn, "network", "WebsocketNetwork") if wn.prioScheme != nil { wn.wg.Add(1) go wn.prioWeightRefresh() @@ -1129,8 +1129,9 @@ func (wn *WebsocketNetwork) maybeSendMessagesOfInterest(peer *wsPeer, messagesOf } } -func (wn *msgHandler) messageHandlerThread(wg *sync.WaitGroup, peersConnectivityCheckCh <-chan time.Time, net networkPeerManager) { +func (wn *msgHandler) messageHandlerThread(wg *sync.WaitGroup, peersConnectivityCheckCh <-chan time.Time, net networkPeerManager, profLabels ...string) { defer wg.Done() + util.SetGoroutineLabels(append(profLabels, "func", "msgHandler.messageHandlerThread")...) for { select { @@ -1231,8 +1232,9 @@ func (wn *msgHandler) sendFilterMessage(msg IncomingMessage, net networkPeerMana } } -func (wn *msgBroadcaster) broadcastThread(wg *sync.WaitGroup, net networkPeerManager) { +func (wn *msgBroadcaster) broadcastThread(wg *sync.WaitGroup, net networkPeerManager, profLabels ...string) { defer wg.Done() + util.SetGoroutineLabels(append(profLabels, "func", "msgHandler.broadcastThread")...) slowWritingPeerCheckTicker := time.NewTicker(wn.slowWritingPeerMonitorInterval) defer slowWritingPeerCheckTicker.Stop() diff --git a/node/node.go b/node/node.go index 58160ff3e0..384bd258f6 100644 --- a/node/node.go +++ b/node/node.go @@ -216,9 +216,9 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd } 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.cryptoPool = execpool.MakePool(node, "worker", "cryptoPool") + node.lowPriorityCryptoVerificationPool = execpool.MakeBacklog(node.cryptoPool, 2*node.cryptoPool.GetParallelism(), execpool.LowPriority, node, "worker", "lowPriorityCryptoVerificationPool") + node.highPriorityCryptoVerificationPool = execpool.MakeBacklog(node.cryptoPool, 2*node.cryptoPool.GetParallelism(), execpool.HighPriority, node, "worker", "highPriorityCryptoVerificationPool") ledgerPaths := ledger.DirsAndPrefix{ DBFilePrefix: config.LedgerFilenamePrefix, ResolvedGenesisDirs: node.genesisDirs, @@ -1061,6 +1061,7 @@ func (node *AlgorandFullNode) OnNewBlock(block bookkeeping.Block, delta ledgerco // don't have to delete key for each block we received. func (node *AlgorandFullNode) oldKeyDeletionThread(done <-chan struct{}) { defer node.monitoringRoutinesWaitGroup.Done() + for { select { case <-done: diff --git a/util/execpool/backlog.go b/util/execpool/backlog.go index 44728d1d9e..c98a2fd427 100644 --- a/util/execpool/backlog.go +++ b/util/execpool/backlog.go @@ -19,6 +19,8 @@ package execpool import ( "context" "sync" + + "github.com/algorand/go-algorand/util" ) // A backlog for an execution pool. The typical usage of this is to @@ -47,7 +49,7 @@ type BacklogPool interface { } // MakeBacklog creates a backlog -func MakeBacklog(execPool ExecutionPool, backlogSize int, priority Priority, owner interface{}) BacklogPool { +func MakeBacklog(execPool ExecutionPool, backlogSize int, priority Priority, owner interface{}, profLabels ...string) BacklogPool { if backlogSize < 0 { return nil } @@ -59,7 +61,7 @@ func MakeBacklog(execPool ExecutionPool, backlogSize int, priority Priority, own bl.ctx, bl.ctxCancel = context.WithCancel(context.Background()) if bl.pool == nil { // create one internally. - bl.pool = MakePool(bl) + bl.pool = MakePool(bl, append(profLabels, "execpool", "internal")...) } if backlogSize == 0 { // use the number of cpus in the system. @@ -68,7 +70,7 @@ func MakeBacklog(execPool ExecutionPool, backlogSize int, priority Priority, own bl.buffer = make(chan backlogItemTask, backlogSize) bl.wg.Add(1) - go bl.worker() + go bl.worker(profLabels) return bl } @@ -129,10 +131,11 @@ func (b *backlog) Shutdown() { } } -func (b *backlog) worker() { +func (b *backlog) worker(profLabels []string) { var t backlogItemTask var ok bool defer b.wg.Done() + util.SetGoroutineLabels(profLabels...) for { diff --git a/util/execpool/pool.go b/util/execpool/pool.go index caa7353ac7..426edd10cb 100644 --- a/util/execpool/pool.go +++ b/util/execpool/pool.go @@ -20,6 +20,8 @@ import ( "context" "runtime" "sync" + + "github.com/algorand/go-algorand/util" ) // The list of all valid priority values. When adding new ones, add them before numPrios. @@ -68,7 +70,7 @@ type enqueuedTask struct { } // MakePool creates a pool. -func MakePool(owner interface{}) ExecutionPool { +func MakePool(owner interface{}, profLabels ...string) ExecutionPool { p := &pool{ inputs: make([]chan enqueuedTask, numPrios), numCPUs: runtime.NumCPU(), @@ -82,9 +84,8 @@ func MakePool(owner interface{}) ExecutionPool { p.wg.Add(p.numCPUs) for i := 0; i < p.numCPUs; i++ { - go p.worker() + go p.worker(profLabels) } - return p } @@ -136,12 +137,14 @@ func (p *pool) Shutdown() { // worker function blocks until a new task is pending on any of the channels and execute the above task. // the implementation below would give higher priority for channels that are on higher priority slot. -func (p *pool) worker() { +func (p *pool) worker(profLabels []string) { var t enqueuedTask var ok bool lowPrio := p.inputs[LowPriority] highPrio := p.inputs[HighPriority] defer p.wg.Done() + util.SetGoroutineLabels(profLabels...) + for { select { diff --git a/util/process.go b/util/process.go index e7ce85ed92..c872b63fe5 100644 --- a/util/process.go +++ b/util/process.go @@ -17,9 +17,11 @@ package util import ( + "context" "io" "os" "os/exec" + "runtime/pprof" "sync" "time" ) @@ -73,3 +75,8 @@ func ExecAndCaptureOutput(command string, args ...string) (string, string, error return string(outputStdout), string(outputStderr), err } + +// SetGoroutineLabels sets profiler labels for identifying goroutines using the pprof package. +func SetGoroutineLabels(args ...string) { + pprof.SetGoroutineLabels(pprof.WithLabels(context.Background(), pprof.Labels(args...))) +} From c8407abca80f4682aac43a5ccc8cd524051f4f63 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 28 Jun 2024 14:16:40 -0400 Subject: [PATCH 162/218] network: p2p traffic exchange for algorand node (#5939) Functional p2p support for gossip network: * DHT advertisement and peers capabilities * Hybrid networking when a node handles both legacy websocket and libp2p networks * HTTP over p2p support for catching up from p2p nodes * P2P network cluster test scenarios * libp2p and DHT metrics and logging handling Co-authored-by: Eric Warehime Co-authored-by: cce <51567+cce@users.noreply.github.com> --- agreement/fuzzer/networkFacade_test.go | 21 +- agreement/gossip/network_test.go | 9 +- catchup/fetcher_test.go | 18 +- catchup/ledgerFetcher.go | 9 +- catchup/ledgerFetcher_test.go | 56 +- catchup/universalFetcher.go | 7 +- cmd/algod/main.go | 4 +- cmd/goal/node.go | 4 +- components/mocks/mockNetwork.go | 37 +- config/config.go | 1 + config/localTemplate.go | 24 +- config/local_defaults.go | 5 +- daemon/algod/server.go | 11 +- data/txHandler.go | 207 +++- data/txHandler_test.go | 3 +- go.mod | 31 +- go.sum | 93 ++ installer/config.json.example | 5 +- logging/log.go | 9 +- netdeploy/remote/nodeConfig.go | 1 + netdeploy/remote/nodecfg/nodeConfigurator.go | 35 + netdeploy/remote/nodecfg/nodeDir.go | 47 + network/addr.go | 64 +- network/addr/addr.go | 88 ++ network/{ => addr}/addr_test.go | 9 +- network/connPerfMon_test.go | 2 +- network/gossipNode.go | 95 +- network/hybridNetwork.go | 233 +++++ network/{ => limitcaller}/dialer.go | 10 +- .../rateLimitingTransport.go | 53 +- network/multiplexer.go | 96 +- network/netidentity.go | 46 +- network/netidentity_test.go | 4 +- network/netprio.go | 2 +- network/p2p/capabilities.go | 179 ++++ network/p2p/capabilities_test.go | 354 +++++++ network/p2p/dht/dht.go | 75 ++ network/p2p/dht/dht_test.go | 64 ++ network/p2p/dnsaddr/resolve.go | 10 +- network/p2p/dnsaddr/resolveController.go | 20 +- network/p2p/dnsaddr/resolve_test.go | 60 +- network/p2p/http.go | 92 ++ network/p2p/logger.go | 123 +++ network/p2p/p2p.go | 203 +++- network/p2p/p2p_test.go | 163 +++- network/p2p/peerID.go | 41 + network/p2p/peerstore/peerstore.go | 90 +- network/p2p/peerstore/peerstore_test.go | 195 ++-- network/p2p/peerstore/utils.go | 4 +- network/p2p/peerstore/utils_test.go | 3 +- network/p2p/pubsub.go | 6 +- network/p2p/streams.go | 70 +- network/p2p/testing/httpNode.go | 122 +++ network/p2pNetwork.go | 617 ++++++++++-- network/p2pNetwork_test.go | 891 +++++++++++++++++- network/{ => phonebook}/phonebook.go | 13 +- network/{ => phonebook}/phonebook_test.go | 2 +- network/requestLogger_test.go | 16 +- network/requestTracker.go | 5 +- network/requestTracker_test.go | 24 +- network/websocketProxy_test.go | 7 +- network/wsNetwork.go | 207 ++-- network/wsNetwork_test.go | 205 ++-- network/wsPeer.go | 26 +- network/wsPeer_test.go | 12 +- node/follower_node.go | 22 +- node/node.go | 47 +- node/node_test.go | 407 ++++++-- rpcs/blockService.go | 40 +- rpcs/blockService_test.go | 60 +- rpcs/healthService_test.go | 3 +- rpcs/httpTxSync.go | 18 +- rpcs/ledgerService.go | 10 +- rpcs/ledgerService_test.go | 59 ++ rpcs/txService_test.go | 101 +- rpcs/txSyncer_test.go | 25 +- test/heapwatch/agreement-log.py | 187 ++++ test/heapwatch/block_history_plot.py | 30 +- test/heapwatch/client_ram_report.py | 4 + test/heapwatch/metrics_aggs.py | 175 ++++ test/heapwatch/metrics_delta.py | 75 +- test/heapwatch/metrics_lib.py | 272 ++++++ test/heapwatch/metrics_viz.py | 191 ++-- test/heapwatch/requirements.txt | 3 + test/heapwatch/topology-extract-p2p.py | 104 ++ test/heapwatch/topology-extract-ws.py | 115 +++ test/heapwatch/topology-viz.py | 75 ++ test/testdata/configs/config-v34.json | 5 +- .../hello-world-small-p2p/genesis.json | 30 + .../recipes/hello-world-small-p2p/net.json | 107 +++ .../recipes/hello-world-small-p2p/recipe.json | 7 + .../hello-world-small-p2p/topology.json | 20 + .../recipes/hello-world-tiny-p2p/genesis.json | 30 + .../hosttemplates.json | 0 .../recipes/hello-world-tiny-p2p/net.json | 101 ++ .../recipes/hello-world-tiny-p2p/recipe.json | 7 + .../hello-world-tiny-p2p/topology.json | 20 + .../recipes/hello-world/genesis.json | 2 +- .../recipes/scenario1s-p2p/Makefile | 23 + .../recipes/scenario1s-p2p/README.md | 16 + .../scenario1s-p2p/copy-node-configs.py | 55 ++ .../recipes/scenario1s-p2p/recipe.json | 7 + .../recipes/scenario1s/Makefile | 4 +- tools/block-generator/go.mod | 23 + tools/block-generator/go.sum | 96 ++ tools/debug/algodump/main.go | 6 +- tools/debug/transplanter/main.go | 6 +- util/metrics/counter.go | 9 +- util/metrics/gauge.go | 9 +- util/metrics/opencensus.go | 172 ++++ util/metrics/opencensus_test.go | 147 +++ util/metrics/prometheus.go | 106 +++ util/metrics/prometheus_test.go | 148 +++ util/metrics/registry.go | 6 + 114 files changed, 7391 insertions(+), 1032 deletions(-) create mode 100644 network/addr/addr.go rename network/{ => addr}/addr_test.go (95%) create mode 100644 network/hybridNetwork.go rename network/{ => limitcaller}/dialer.go (91%) rename network/{ => limitcaller}/rateLimitingTransport.go (55%) create mode 100644 network/p2p/capabilities.go create mode 100644 network/p2p/capabilities_test.go create mode 100644 network/p2p/dht/dht.go create mode 100644 network/p2p/dht/dht_test.go create mode 100644 network/p2p/http.go create mode 100644 network/p2p/logger.go create mode 100644 network/p2p/testing/httpNode.go rename network/{ => phonebook}/phonebook.go (96%) rename network/{ => phonebook}/phonebook_test.go (99%) create mode 100644 test/heapwatch/agreement-log.py create mode 100644 test/heapwatch/metrics_aggs.py create mode 100644 test/heapwatch/metrics_lib.py create mode 100644 test/heapwatch/topology-extract-p2p.py create mode 100644 test/heapwatch/topology-extract-ws.py create mode 100644 test/heapwatch/topology-viz.py create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-small-p2p/genesis.json create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-small-p2p/net.json create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-small-p2p/recipe.json create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-small-p2p/topology.json create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/genesis.json rename test/testdata/deployednettemplates/recipes/{hello-world => hello-world-tiny-p2p}/hosttemplates.json (100%) create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/net.json create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/recipe.json create mode 100644 test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/topology.json create mode 100644 test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile create mode 100644 test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md create mode 100644 test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py create mode 100644 test/testdata/deployednettemplates/recipes/scenario1s-p2p/recipe.json create mode 100644 util/metrics/opencensus.go create mode 100644 util/metrics/opencensus_test.go create mode 100644 util/metrics/prometheus.go create mode 100644 util/metrics/prometheus_test.go diff --git a/agreement/fuzzer/networkFacade_test.go b/agreement/fuzzer/networkFacade_test.go index 131361e3e7..804fb1e7ff 100644 --- a/agreement/fuzzer/networkFacade_test.go +++ b/agreement/fuzzer/networkFacade_test.go @@ -70,9 +70,16 @@ type NetworkFacade struct { rand *rand.Rand timeoutAtInitOnce sync.Once timeoutAtInitWait sync.WaitGroup - peerToNode map[network.Peer]int + peerToNode map[*facadePeer]int } +type facadePeer struct { + id int + net network.GossipNode +} + +func (p *facadePeer) GetNetwork() network.GossipNode { return p.net } + // MakeNetworkFacade creates a facade with a given nodeID. func MakeNetworkFacade(fuzzer *Fuzzer, nodeID int) *NetworkFacade { n := &NetworkFacade{ @@ -83,12 +90,12 @@ func MakeNetworkFacade(fuzzer *Fuzzer, nodeID int) *NetworkFacade { eventsQueues: make(map[string]int), eventsQueuesCh: make(chan int, 1000), rand: rand.New(rand.NewSource(int64(nodeID))), - peerToNode: make(map[network.Peer]int, fuzzer.nodesCount), + peerToNode: make(map[*facadePeer]int, fuzzer.nodesCount), debugMessages: false, } n.timeoutAtInitWait.Add(1) for i := 0; i < fuzzer.nodesCount; i++ { - n.peerToNode[network.Peer(new(int))] = i + n.peerToNode[&facadePeer{id: i, net: n}] = i } return n } @@ -179,7 +186,7 @@ func (n *NetworkFacade) WaitForEventsQueue(cleared bool) { func (n *NetworkFacade) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, exclude network.Peer) error { excludeNode := -1 if exclude != nil { - excludeNode = n.peerToNode[exclude] + excludeNode = n.peerToNode[exclude.(*facadePeer)] } return n.broadcast(tag, data, excludeNode, "NetworkFacade service-%v Broadcast %v %v\n") } @@ -240,7 +247,7 @@ func (n *NetworkFacade) PushDownstreamMessage(newMsg context.CancelFunc) bool { func (n *NetworkFacade) Address() (string, bool) { return "mock network", true } // Start - unused function -func (n *NetworkFacade) Start() {} +func (n *NetworkFacade) Start() error { return nil } // Stop - unused function func (n *NetworkFacade) Stop() {} @@ -341,8 +348,8 @@ func (n *NetworkFacade) ReceiveMessage(sourceNode int, tag protocol.Tag, data [] n.pushPendingReceivedMessage() } -func (n *NetworkFacade) Disconnect(sender network.Peer) { - sourceNode := n.peerToNode[sender] +func (n *NetworkFacade) Disconnect(sender network.DisconnectablePeer) { + sourceNode := n.peerToNode[sender.(*facadePeer)] n.fuzzer.Disconnect(n.nodeID, sourceNode) } diff --git a/agreement/gossip/network_test.go b/agreement/gossip/network_test.go index c168b77cce..a3c5328716 100644 --- a/agreement/gossip/network_test.go +++ b/agreement/gossip/network_test.go @@ -18,7 +18,6 @@ package gossip import ( "context" - "net" "net/http" "sync" "sync/atomic" @@ -136,7 +135,7 @@ func (w *whiteholeNetwork) Relay(ctx context.Context, tag protocol.Tag, data []b func (w *whiteholeNetwork) BroadcastSimple(tag protocol.Tag, data []byte) error { return w.Broadcast(context.Background(), tag, data, true, nil) } -func (w *whiteholeNetwork) Disconnect(badnode network.Peer) { +func (w *whiteholeNetwork) Disconnect(badnode network.DisconnectablePeer) { return } func (w *whiteholeNetwork) DisconnectPeers() { @@ -156,11 +155,11 @@ func (w *whiteholeNetwork) GetPeers(options ...network.PeerOption) []network.Pee } func (w *whiteholeNetwork) RegisterHTTPHandler(path string, handler http.Handler) { } -func (w *whiteholeNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { +func (w *whiteholeNetwork) GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettableConn) { return nil } -func (w *whiteholeNetwork) Start() { +func (w *whiteholeNetwork) Start() error { w.quit = make(chan struct{}) go func(w *whiteholeNetwork) { w.domain.messagesMu.Lock() @@ -216,7 +215,7 @@ func (w *whiteholeNetwork) Start() { atomic.AddUint32(&w.lastMsgRead, 1) } }(w) - return + return nil } func (w *whiteholeNetwork) getMux() *network.Multiplexer { return w.mux diff --git a/catchup/fetcher_test.go b/catchup/fetcher_test.go index e219852bf4..52b0b32a8f 100644 --- a/catchup/fetcher_test.go +++ b/catchup/fetcher_test.go @@ -21,7 +21,6 @@ import ( "net" "net/http" "net/url" - "strings" "testing" "github.com/gorilla/mux" @@ -173,8 +172,8 @@ func (b *basicRPCNode) GetPeers(options ...network.PeerOption) []network.Peer { return b.peers } -func (b *basicRPCNode) SubstituteGenesisID(rawURL string) string { - return strings.Replace(rawURL, "{genesisID}", "test genesisID", -1) +func (b *basicRPCNode) GetGenesisID() string { + return "test genesisID" } type httpTestPeerSource struct { @@ -191,8 +190,8 @@ func (s *httpTestPeerSource) RegisterHandlers(dispatch []network.TaggedMessageHa s.dispatchHandlers = append(s.dispatchHandlers, dispatch...) } -func (s *httpTestPeerSource) SubstituteGenesisID(rawURL string) string { - return strings.Replace(rawURL, "{genesisID}", "test genesisID", -1) +func (s *httpTestPeerSource) GetGenesisID() string { + return "test genesisID" } // implement network.HTTPPeer @@ -201,8 +200,13 @@ type testHTTPPeer string func (p *testHTTPPeer) GetAddress() string { return string(*p) } + func (p *testHTTPPeer) GetHTTPClient() *http.Client { - return &http.Client{} + return &http.Client{ + Transport: &network.HTTPPAddressBoundTransport{ + Addr: p.GetAddress(), + InnerTransport: http.DefaultTransport}, + } } func (p *testHTTPPeer) GetHTTPPeer() network.HTTPPeer { return p @@ -238,6 +242,8 @@ func (p *testUnicastPeer) GetAddress() string { return "test" } +func (p *testUnicastPeer) GetNetwork() network.GossipNode { return p.gn } + func (p *testUnicastPeer) Request(ctx context.Context, tag protocol.Tag, topics network.Topics) (resp *network.Response, e error) { responseChannel := make(chan *network.Response, 1) diff --git a/catchup/ledgerFetcher.go b/catchup/ledgerFetcher.go index 2f87b5d576..916627db8f 100644 --- a/catchup/ledgerFetcher.go +++ b/catchup/ledgerFetcher.go @@ -23,7 +23,6 @@ import ( "fmt" "io" "net/http" - "path" "strconv" "time" @@ -74,13 +73,7 @@ func makeLedgerFetcher(net network.GossipNode, accessor ledger.CatchpointCatchup } func (lf *ledgerFetcher) requestLedger(ctx context.Context, peer network.HTTPPeer, round basics.Round, method string) (*http.Response, error) { - parsedURL, err := network.ParseHostOrURL(peer.GetAddress()) - if err != nil { - return nil, err - } - - parsedURL.Path = lf.net.SubstituteGenesisID(path.Join(parsedURL.Path, "/v1/{genesisID}/ledger/"+strconv.FormatUint(uint64(round), 36))) - ledgerURL := parsedURL.String() + ledgerURL := network.SubstituteGenesisID(lf.net, "/v1/{genesisID}/ledger/"+strconv.FormatUint(uint64(round), 36)) lf.log.Debugf("ledger %s %#v peer %#v %T", method, ledgerURL, peer, peer) request, err := http.NewRequestWithContext(ctx, method, ledgerURL, nil) if err != nil { diff --git a/catchup/ledgerFetcher_test.go b/catchup/ledgerFetcher_test.go index 6bbde32120..a080aca31e 100644 --- a/catchup/ledgerFetcher_test.go +++ b/catchup/ledgerFetcher_test.go @@ -17,6 +17,7 @@ package catchup import ( + "archive/tar" "context" "fmt" "net" @@ -30,6 +31,8 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/logging" + p2ptesting "github.com/algorand/go-algorand/network/p2p/testing" + "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -125,7 +128,7 @@ func TestLedgerFetcherErrorResponseHandling(t *testing.T) { } } -func TestLedgerFetcherHeadLedger(t *testing.T) { +func TestLedgerFetcher(t *testing.T) { partitiontest.PartitionTest(t) // create a dummy server. @@ -136,16 +139,19 @@ func TestLedgerFetcherHeadLedger(t *testing.T) { listener, err := net.Listen("tcp", "localhost:") var httpServerResponse = 0 - var contentTypes = make([]string, 0) require.NoError(t, err) go s.Serve(listener) defer s.Close() defer listener.Close() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - for _, contentType := range contentTypes { - w.Header().Add("Content-Type", contentType) + if req.Method == http.MethodHead { + w.WriteHeader(httpServerResponse) + } else { + w.Header().Add("Content-Type", rpcs.LedgerResponseContentType) + w.WriteHeader(httpServerResponse) + wtar := tar.NewWriter(w) + wtar.Close() } - w.WriteHeader(httpServerResponse) }) successPeer := testHTTPPeer(listener.Addr().String()) lf := makeLedgerFetcher(&mocks.MockNetwork{}, &mocks.MockCatchpointCatchupAccessor{}, logging.TestingLog(t), &dummyLedgerFetcherReporter{}, config.GetDefaultLocal()) @@ -157,7 +163,7 @@ func TestLedgerFetcherHeadLedger(t *testing.T) { // headLedger parseURL failure parseFailurePeer := testHTTPPeer("foobar") err = lf.headLedger(context.Background(), &parseFailurePeer, basics.Round(0)) - require.Equal(t, fmt.Errorf("could not parse a host from url"), err) + require.ErrorContains(t, err, "could not parse a host from url") // headLedger 404 response httpServerResponse = http.StatusNotFound @@ -169,8 +175,46 @@ func TestLedgerFetcherHeadLedger(t *testing.T) { err = lf.headLedger(context.Background(), &successPeer, basics.Round(0)) require.NoError(t, err) + httpServerResponse = http.StatusOK + err = lf.downloadLedger(context.Background(), &successPeer, basics.Round(0)) + require.NoError(t, err) + // headLedger 500 response httpServerResponse = http.StatusInternalServerError err = lf.headLedger(context.Background(), &successPeer, basics.Round(0)) require.Equal(t, fmt.Errorf("headLedger error response status code %d", http.StatusInternalServerError), err) } + +func TestLedgerFetcherP2P(t *testing.T) { + partitiontest.PartitionTest(t) + + mux := http.NewServeMux() + nodeA := p2ptesting.MakeHTTPNode(t) + nodeA.RegisterHTTPHandler("/v1/ledger/0", mux) + var httpServerResponse = 0 + mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodHead { + w.WriteHeader(httpServerResponse) + } else { + w.Header().Add("Content-Type", rpcs.LedgerResponseContentType) + w.WriteHeader(httpServerResponse) + wtar := tar.NewWriter(w) + wtar.Close() + } + }) + + nodeA.Start() + defer nodeA.Stop() + + successPeer := nodeA.GetHTTPPeer() + lf := makeLedgerFetcher(nodeA, &mocks.MockCatchpointCatchupAccessor{}, logging.TestingLog(t), &dummyLedgerFetcherReporter{}, config.GetDefaultLocal()) + + // headLedger 200 response + httpServerResponse = http.StatusOK + err := lf.headLedger(context.Background(), successPeer, basics.Round(0)) + require.NoError(t, err) + + httpServerResponse = http.StatusOK + err = lf.downloadLedger(context.Background(), successPeer, basics.Round(0)) + require.NoError(t, err) +} diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index 31d82adc98..27b970fc26 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -219,13 +219,8 @@ type HTTPFetcher struct { // getBlockBytes gets a block. // Core piece of FetcherClient interface func (hf *HTTPFetcher) getBlockBytes(ctx context.Context, r basics.Round) (data []byte, err error) { - parsedURL, err := network.ParseHostOrURL(hf.rootURL) - if err != nil { - return nil, err - } + blockURL := rpcs.FormatBlockQuery(uint64(r), "", hf.net) - parsedURL.Path = rpcs.FormatBlockQuery(uint64(r), parsedURL.Path, hf.net) - blockURL := parsedURL.String() hf.log.Debugf("block GET %#v peer %#v %T", blockURL, hf.peer, hf.peer) request, err := http.NewRequest("GET", blockURL, nil) if err != nil { diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 0f93ed447f..09770cb6e0 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -33,7 +33,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" - "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" toolsnet "github.com/algorand/go-algorand/tools/network" "github.com/algorand/go-algorand/util" @@ -282,7 +282,7 @@ func run() int { // make sure that the format of each entry is valid: for idx, peer := range peerOverrideArray { - addr, addrErr := network.ParseHostOrURLOrMultiaddr(peer) + addr, addrErr := addr.ParseHostOrURLOrMultiaddr(peer) if addrErr != nil { fmt.Fprintf(os.Stderr, "Provided command line parameter '%s' is not a valid host:port pair\n", peer) return 1 diff --git a/cmd/goal/node.go b/cmd/goal/node.go index 17de96a81a..2db08fd4e5 100644 --- a/cmd/goal/node.go +++ b/cmd/goal/node.go @@ -39,7 +39,7 @@ import ( "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/libgoal" - "github.com/algorand/go-algorand/network" + naddr "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/tokens" @@ -751,7 +751,7 @@ func verifyPeerDialArg() bool { // make sure that the format of each entry is valid: for _, peer := range strings.Split(peerDial, ";") { - _, err := network.ParseHostOrURLOrMultiaddr(peer) + _, err := naddr.ParseHostOrURLOrMultiaddr(peer) if err != nil { reportErrorf("Provided peer '%s' is not a valid peer address : %v", peer, err) return false diff --git a/components/mocks/mockNetwork.go b/components/mocks/mockNetwork.go index 25486cb050..f933a553a9 100644 --- a/components/mocks/mockNetwork.go +++ b/components/mocks/mockNetwork.go @@ -18,7 +18,7 @@ package mocks import ( "context" - "net" + "errors" "net/http" "github.com/algorand/go-algorand/network" @@ -28,6 +28,7 @@ import ( // MockNetwork is a dummy network that doesn't do anything type MockNetwork struct { network.GossipNode + GenesisID string } // Broadcast - unused function @@ -46,7 +47,8 @@ func (network *MockNetwork) Address() (string, bool) { } // Start - unused function -func (network *MockNetwork) Start() { +func (network *MockNetwork) Start() error { + return nil } // Stop - unused function @@ -58,7 +60,7 @@ func (network *MockNetwork) RequestConnectOutgoing(replace bool, quit <-chan str } // Disconnect - unused function -func (network *MockNetwork) Disconnect(badpeer network.Peer) { +func (network *MockNetwork) Disconnect(badpeer network.DisconnectablePeer) { } // DisconnectPeers - unused function @@ -74,11 +76,6 @@ func (network *MockNetwork) GetPeers(options ...network.PeerOption) []network.Pe return nil } -// GetRoundTripper -- returns the network round tripper -func (network *MockNetwork) GetRoundTripper() http.RoundTripper { - return http.DefaultTransport -} - // Ready - always ready func (network *MockNetwork) Ready() chan struct{} { c := make(chan struct{}) @@ -94,6 +91,14 @@ func (network *MockNetwork) RegisterHandlers(dispatch []network.TaggedMessageHan func (network *MockNetwork) ClearHandlers() { } +// RegisterProcessors - empty implementation. +func (network *MockNetwork) RegisterProcessors(dispatch []network.TaggedMessageProcessor) { +} + +// ClearProcessors - empty implementation +func (network *MockNetwork) ClearProcessors() { +} + // RegisterHTTPHandler - empty implementation func (network *MockNetwork) RegisterHTTPHandler(path string, handler http.Handler) { } @@ -102,11 +107,19 @@ func (network *MockNetwork) RegisterHTTPHandler(path string, handler http.Handle func (network *MockNetwork) OnNetworkAdvance() {} // GetHTTPRequestConnection - empty implementation -func (network *MockNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { +func (network *MockNetwork) GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettableConn) { return nil } -// SubstituteGenesisID - empty implementation -func (network *MockNetwork) SubstituteGenesisID(rawURL string) string { - return rawURL +// GetGenesisID - empty implementation +func (network *MockNetwork) GetGenesisID() string { + if network.GenesisID == "" { + return "mocknet" + } + return network.GenesisID +} + +// GetHTTPClient returns a http.Client with a suitable for the network +func (network *MockNetwork) GetHTTPClient(address string) (*http.Client, error) { + return nil, errors.New("not implemented") } diff --git a/config/config.go b/config/config.go index a8beb05800..2d5d0bdbfe 100644 --- a/config/config.go +++ b/config/config.go @@ -268,6 +268,7 @@ const ( dnssecSRV = 1 << iota dnssecRelayAddr dnssecTelemetryAddr + dnssecTXT ) const ( diff --git a/config/localTemplate.go b/config/localTemplate.go index 309ffcb798..314b83a78b 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -362,8 +362,9 @@ type Local struct { // 0x01 (dnssecSRV) - validate SRV response // 0x02 (dnssecRelayAddr) - validate relays' names to addresses resolution // 0x04 (dnssecTelemetryAddr) - validate telemetry and metrics names to addresses resolution + // 0x08 (dnssecTXT) - validate TXT response // ... - DNSSecurityFlags uint32 `version[6]:"1"` + DNSSecurityFlags uint32 `version[6]:"1" version[34]:"9"` // EnablePingHandler controls whether the gossip node would respond to ping messages with a pong message. EnablePingHandler bool `version[6]:"true"` @@ -596,9 +597,19 @@ type Local struct { // When it exceeds this capacity, it redirects the block requests to a different node BlockServiceMemCap uint64 `version[28]:"500000000"` - // EnableP2P turns on the peer to peer network + // EnableP2P turns on the peer to peer network. + // When both EnableP2P and EnableP2PHybridMode (below) are set, EnableP2PHybridMode takes precedence. EnableP2P bool `version[31]:"false"` + // EnableP2PHybridMode turns on both websockets and P2P networking. + EnableP2PHybridMode bool `version[34]:"false"` + + // P2PNetAddress sets the listen address used for P2P networking, if hybrid mode is set. + P2PNetAddress string `version[34]:""` + + // EnableDHT will turn on the hash table for use with capabilities advertisement + EnableDHTProviders bool `version[34]:"false"` + // P2PPersistPeerID will write the private key used for the node's PeerID to the P2PPrivateKeyLocation. // This is only used when P2PEnable is true. If P2PPrivateKey is not specified, it uses the default location. P2PPersistPeerID bool `version[29]:"false"` @@ -683,11 +694,16 @@ func (cfg Local) DNSSecurityRelayAddrEnforced() bool { return cfg.DNSSecurityFlags&dnssecRelayAddr != 0 } -// DNSSecurityTelemeryAddrEnforced returns true if relay name to ip addr resolution enforced -func (cfg Local) DNSSecurityTelemeryAddrEnforced() bool { +// DNSSecurityTelemetryAddrEnforced returns true if relay name to ip addr resolution enforced +func (cfg Local) DNSSecurityTelemetryAddrEnforced() bool { return cfg.DNSSecurityFlags&dnssecTelemetryAddr != 0 } +// DNSSecurityTXTEnforced returns true if TXT response verification enforced +func (cfg Local) DNSSecurityTXTEnforced() bool { + return cfg.DNSSecurityFlags&dnssecTXT != 0 +} + // CatchupVerifyCertificate returns true if certificate verification is needed func (cfg Local) CatchupVerifyCertificate() bool { return cfg.CatchupBlockValidateMode&catchupValidationModeCertificate == 0 diff --git a/config/local_defaults.go b/config/local_defaults.go index f5f02082aa..ae2ed22ebf 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -51,7 +51,7 @@ var defaultLocal = Local{ ConnectionsRateLimitingWindowSeconds: 1, CrashDBDir: "", DNSBootstrapID: ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)", - DNSSecurityFlags: 1, + DNSSecurityFlags: 9, DeadlockDetection: 0, DeadlockDetectionThreshold: 30, DisableAPIAuth: false, @@ -64,6 +64,7 @@ var defaultLocal = Local{ EnableAgreementTimeMetrics: false, EnableAssembleStats: false, EnableBlockService: false, + EnableDHTProviders: false, EnableDeveloperAPI: false, EnableExperimentalAPI: false, EnableFollowMode: false, @@ -74,6 +75,7 @@ var defaultLocal = Local{ EnableMetricReporting: false, EnableOutgoingNetworkMessageFiltering: true, EnableP2P: false, + EnableP2PHybridMode: false, EnablePingHandler: true, EnableProcessBlockStats: false, EnableProfiler: false, @@ -117,6 +119,7 @@ var defaultLocal = Local{ OptimizeAccountsDatabaseOnStartup: false, OutgoingMessageFilterBucketCount: 3, OutgoingMessageFilterBucketSize: 128, + P2PNetAddress: "", P2PPersistPeerID: false, P2PPrivateKeyLocation: "", ParticipationKeysRefreshInterval: 60000000000, diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 13432957c3..c43b0b0693 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -59,7 +59,7 @@ const maxHeaderBytes = 4096 type ServerNode interface { apiServer.APINodeInterface ListeningAddress() (string, bool) - Start() + Start() error Stop() } @@ -298,7 +298,13 @@ func getPortFromAddress(addr string) (string, error) { func (s *Server) Start() { s.log.Info("Trying to start an Algorand node") fmt.Print("Initializing the Algorand node... ") - s.node.Start() + err := s.node.Start() + if err != nil { + msg := fmt.Sprintf("Failed to start an Algorand node: %v", err) + s.log.Error(msg) + fmt.Println(msg) + os.Exit(1) + } s.log.Info("Successfully started an Algorand node.") fmt.Println("Success!") @@ -317,7 +323,6 @@ func (s *Server) Start() { } var apiToken string - var err error fmt.Printf("API authentication disabled: %v\n", cfg.DisableAPIAuth) if !cfg.DisableAPIAuth { apiToken, err = tokens.GetAndValidateAPIToken(s.RootPath, tokens.AlgodTokenFilename) diff --git a/data/txHandler.go b/data/txHandler.go index 871e71cbc3..eae9586c47 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -240,9 +240,26 @@ func (handler *TxHandler) Start() { if handler.msgCache != nil { handler.msgCache.Start(handler.ctx, 60*time.Second) } + // wsNetwork handler handler.net.RegisterHandlers([]network.TaggedMessageHandler{ {Tag: protocol.TxnTag, MessageHandler: network.HandlerFunc(handler.processIncomingTxn)}, }) + + // libp2p pubsub validator and handler abstracted as TaggedMessageProcessor + handler.net.RegisterProcessors([]network.TaggedMessageProcessor{ + { + Tag: protocol.TxnTag, + // create anonymous struct to hold the two functions and satisfy the network.MessageProcessor interface + MessageHandler: struct { + network.ProcessorValidateFunc + network.ProcessorHandleFunc + }{ + network.ProcessorValidateFunc(handler.validateIncomingTxMessage), + network.ProcessorHandleFunc(handler.processIncomingTxMessage), + }, + }, + }) + handler.backlogWg.Add(2) go handler.backlogWorker() go handler.backlogGaugeThread() @@ -533,7 +550,7 @@ func (handler *TxHandler) deleteFromCaches(msgKey *crypto.Digest, canonicalKey * // dedupCanonical checks if the transaction group has been seen before after reencoding to canonical representation. // returns a key used for insertion if the group was not found. -func (handler *TxHandler) dedupCanonical(ntx int, unverifiedTxGroup []transactions.SignedTxn, consumed int) (key *crypto.Digest, isDup bool) { +func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.SignedTxn, consumed int) (key *crypto.Digest, isDup bool) { // consider situations where someone want to censor transactions A // 1. Txn A is not part of a group => txn A with a valid signature is OK // Censorship attempts are: @@ -550,6 +567,7 @@ func (handler *TxHandler) dedupCanonical(ntx int, unverifiedTxGroup []transactio // - using individual txn from a group: {A, Z} could be poisoned by {A, B}, where B is invalid var d crypto.Digest + ntx := len(unverifiedTxGroup) if ntx == 1 { // a single transaction => cache/dedup canonical txn with its signature enc := unverifiedTxGroup[0].MarshalMsg(nil) @@ -577,61 +595,58 @@ func (handler *TxHandler) dedupCanonical(ntx int, unverifiedTxGroup []transactio return &d, false } -// processIncomingTxn decodes a transaction group from incoming message and enqueues into the back log for processing. -// The function also performs some input data pre-validation; -// - txn groups are cut to MaxTxGroupSize size -// - message are checked for duplicates -// - transactions are checked for duplicates - -func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) network.OutgoingMessage { +// incomingMsgDupCheck runs the duplicate check on a raw incoming message. +// Returns: +// - the key used for insertion if the message was not found in the cache +// - a boolean indicating if the message was a duplicate +func (handler *TxHandler) incomingMsgDupCheck(data []byte) (*crypto.Digest, bool) { var msgKey *crypto.Digest var isDup bool if handler.msgCache != nil { // check for duplicate messages // this helps against relaying duplicates - if msgKey, isDup = handler.msgCache.CheckAndPut(rawmsg.Data); isDup { + if msgKey, isDup = handler.msgCache.CheckAndPut(data); isDup { transactionMessagesDupRawMsg.Inc(nil) - return network.OutgoingMessage{Action: network.Ignore} + return msgKey, true } } + return msgKey, false +} - unverifiedTxGroup := make([]transactions.SignedTxn, 1) - dec := protocol.NewMsgpDecoderBytes(rawmsg.Data) - ntx := 0 - consumed := 0 - - var err error +// incomingMsgErlCheck runs the rate limiting check on a sender. +// Returns: +// - the capacity guard returned by the elastic rate limiter +// - a boolean indicating if the sender is rate limited +func (handler *TxHandler) incomingMsgErlCheck(sender network.DisconnectablePeer) (*util.ErlCapacityGuard, bool) { var capguard *util.ErlCapacityGuard - accepted := false - defer func() { - // if we failed to put the item onto the backlog, we should release the capacity if any - if !accepted { - if capguard != nil { - if capErr := capguard.Release(); capErr != nil { - logging.Base().Warnf("Failed to release capacity to ElasticRateLimiter: %v", capErr) - } - } - } - }() - + var err error if handler.erl != nil { congestedERL := float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue)) // consume a capacity unit // if the elastic rate limiter cannot vend a capacity, the error it returns // is sufficient to indicate that we should enable Congestion Control, because // an issue in vending capacity indicates the underlying resource (TXBacklog) is full - capguard, err = handler.erl.ConsumeCapacity(rawmsg.Sender.(util.ErlClient)) + capguard, err = handler.erl.ConsumeCapacity(sender.(util.ErlClient)) if err != nil { handler.erl.EnableCongestionControl() // if there is no capacity, it is the same as if we failed to put the item onto the backlog, so report such transactionMessagesDroppedFromBacklog.Inc(nil) - return network.OutgoingMessage{Action: network.Ignore} + return capguard, true } // if the backlog Queue has 50% of its buffer back, turn congestion control off if !congestedERL { handler.erl.DisableCongestionControl() } } + return capguard, false +} + +// decodeMsg decodes TX message buffer into transactions.SignedTxn, +// and returns number of bytes consumed from the buffer and a boolean indicating if the message was invalid. +func decodeMsg(data []byte) (unverifiedTxGroup []transactions.SignedTxn, consumed int, invalid bool) { + unverifiedTxGroup = make([]transactions.SignedTxn, 1) + dec := protocol.NewMsgpDecoderBytes(data) + ntx := 0 for { if len(unverifiedTxGroup) == ntx { @@ -645,7 +660,7 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net break } logging.Base().Warnf("Received a non-decodable txn: %v", err) - return network.OutgoingMessage{Action: network.Disconnect} + return nil, 0, true } consumed = dec.Consumed() ntx++ @@ -654,13 +669,13 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net if dec.Remaining() > 0 { // if something else left in the buffer - this is an error, drop transactionMessageTxGroupExcessive.Inc(nil) - return network.OutgoingMessage{Action: network.Disconnect} + return nil, 0, true } } } if ntx == 0 { logging.Base().Warnf("Received empty tx group") - return network.OutgoingMessage{Action: network.Disconnect} + return nil, 0, true } unverifiedTxGroup = unverifiedTxGroup[:ntx] @@ -669,22 +684,72 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net transactionMessageTxGroupFull.Inc(nil) } + return unverifiedTxGroup, consumed, false +} + +// incomingTxGroupDupRateLimit checks +// - if the incoming transaction group has been seen before after reencoding to canonical representation, and +// - if the sender is rate limited by the per-application rate limiter. +func (handler *TxHandler) incomingTxGroupDupRateLimit(unverifiedTxGroup []transactions.SignedTxn, encodedExpectedSize int, sender network.DisconnectablePeer) (*crypto.Digest, bool) { var canonicalKey *crypto.Digest if handler.txCanonicalCache != nil { - if canonicalKey, isDup = handler.dedupCanonical(ntx, unverifiedTxGroup, consumed); isDup { + var isDup bool + if canonicalKey, isDup = handler.dedupCanonical(unverifiedTxGroup, encodedExpectedSize); isDup { transactionMessagesDupCanonical.Inc(nil) - return network.OutgoingMessage{Action: network.Ignore} + return canonicalKey, true } } // rate limit per application in a group. Limiting any app in a group drops the entire message. if handler.appLimiter != nil { congestedARL := len(handler.backlogQueue) > handler.appLimiterBacklogThreshold - if congestedARL && handler.appLimiter.shouldDrop(unverifiedTxGroup, rawmsg.Sender.(network.IPAddressable).RoutingAddr()) { + if congestedARL && handler.appLimiter.shouldDrop(unverifiedTxGroup, sender.(network.IPAddressable).RoutingAddr()) { transactionMessagesAppLimiterDrop.Inc(nil) - return network.OutgoingMessage{Action: network.Ignore} + return canonicalKey, true } } + return canonicalKey, false +} + +// processIncomingTxn decodes a transaction group from incoming message and enqueues into the back log for processing. +// The function also performs some input data pre-validation; +// - txn groups are cut to MaxTxGroupSize size +// - message are checked for duplicates +// - transactions are checked for duplicates +func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) network.OutgoingMessage { + msgKey, shouldDrop := handler.incomingMsgDupCheck(rawmsg.Data) + if shouldDrop { + return network.OutgoingMessage{Action: network.Ignore} + } + + capguard, shouldDrop := handler.incomingMsgErlCheck(rawmsg.Sender) + accepted := false + defer func() { + // if we failed to put the item onto the backlog, we should release the capacity if any + if !accepted && capguard != nil { + if capErr := capguard.Release(); capErr != nil { + logging.Base().Warnf("processIncomingTxn: failed to release capacity to ElasticRateLimiter: %v", capErr) + } + } + }() + + if shouldDrop { + // this TX message was rate-limited by ERL + return network.OutgoingMessage{Action: network.Ignore} + } + + unverifiedTxGroup, consumed, invalid := decodeMsg(rawmsg.Data) + if invalid { + // invalid encoding or exceeding txgroup, disconnect from this peer + return network.OutgoingMessage{Action: network.Disconnect} + } + + canonicalKey, drop := handler.incomingTxGroupDupRateLimit(unverifiedTxGroup, consumed, rawmsg.Sender) + if drop { + // this re-serialized txgroup was detected as a duplicate by the canonical message cache, + // or it was rate-limited by the per-app rate limiter + return network.OutgoingMessage{Action: network.Ignore} + } select { case handler.backlogQueue <- &txBacklogMsg{ @@ -701,14 +766,70 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net transactionMessagesDroppedFromBacklog.Inc(nil) // additionally, remove the txn from duplicate caches to ensure it can be re-submitted - if handler.txCanonicalCache != nil && canonicalKey != nil { - handler.txCanonicalCache.Delete(canonicalKey) - } - if handler.msgCache != nil && msgKey != nil { - handler.msgCache.DeleteByKey(msgKey) - } + handler.deleteFromCaches(msgKey, canonicalKey) + } + + return network.OutgoingMessage{Action: network.Ignore} +} + +type validatedIncomingTxMessage struct { + rawmsg network.IncomingMessage + unverifiedTxGroup []transactions.SignedTxn + msgKey *crypto.Digest + canonicalKey *crypto.Digest +} + +// validateIncomingTxMessage is the validator for the MessageProcessor implementation used by P2PNetwork. +func (handler *TxHandler) validateIncomingTxMessage(rawmsg network.IncomingMessage) network.ValidatedMessage { + msgKey, isDup := handler.incomingMsgDupCheck(rawmsg.Data) + if isDup { + return network.ValidatedMessage{Action: network.Ignore, ValidatedMessage: nil} } + unverifiedTxGroup, consumed, invalid := decodeMsg(rawmsg.Data) + if invalid { + // invalid encoding or exceeding txgroup, disconnect from this peer + return network.ValidatedMessage{Action: network.Disconnect, ValidatedMessage: nil} + } + + canonicalKey, drop := handler.incomingTxGroupDupRateLimit(unverifiedTxGroup, consumed, rawmsg.Sender) + if drop { + // this re-serialized txgroup was detected as a duplicate by the canonical message cache, + // or it was rate-limited by the per-app rate limiter + return network.ValidatedMessage{Action: network.Ignore, ValidatedMessage: nil} + } + + return network.ValidatedMessage{ + Action: network.Accept, + Tag: rawmsg.Tag, + ValidatedMessage: &validatedIncomingTxMessage{ + rawmsg: rawmsg, + unverifiedTxGroup: unverifiedTxGroup, + msgKey: msgKey, + canonicalKey: canonicalKey, + }, + } +} + +// processIncomingTxMessage is the handler for the MessageProcessor implementation used by P2PNetwork. +func (handler *TxHandler) processIncomingTxMessage(validatedMessage network.ValidatedMessage) network.OutgoingMessage { + msg := validatedMessage.ValidatedMessage.(*validatedIncomingTxMessage) + select { + case handler.backlogQueue <- &txBacklogMsg{ + rawmsg: &msg.rawmsg, + unverifiedTxGroup: msg.unverifiedTxGroup, + rawmsgDataHash: msg.msgKey, + unverifiedTxGroupHash: msg.canonicalKey, + capguard: nil, + }: + default: + // if we failed here we want to increase the corresponding metric. It might suggest that we + // want to increase the queue size. + transactionMessagesDroppedFromBacklog.Inc(nil) + + // additionally, remove the txn from duplicate caches to ensure it can be re-submitted + handler.deleteFromCaches(msg.msgKey, msg.canonicalKey) + } return network.OutgoingMessage{Action: network.Ignore} } diff --git a/data/txHandler_test.go b/data/txHandler_test.go index d395779f33..9237865037 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -61,7 +61,8 @@ var txBacklogSize = config.GetDefaultLocal().TxBacklogSize // mock sender is used to implement OnClose, since TXHandlers expect to use Senders and ERL Clients type mockSender struct{} -func (m mockSender) OnClose(func()) {} +func (m mockSender) OnClose(func()) {} +func (m mockSender) GetNetwork() network.GossipNode { panic("not implemented") } func (m mockSender) IPAddr() []byte { return nil } func (m mockSender) RoutingAddr() []byte { return nil } diff --git a/go.mod b/go.mod index a32da9da8c..e4d8c5b8d5 100644 --- a/go.mod +++ b/go.mod @@ -28,10 +28,13 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.0.0 github.com/gorilla/mux v1.8.0 + github.com/ipfs/go-log v1.0.5 + github.com/ipfs/go-log/v2 v2.5.1 github.com/jmoiron/sqlx v1.2.0 github.com/karalabe/usb v0.0.2 github.com/labstack/echo/v4 v4.9.1 github.com/libp2p/go-libp2p v0.33.2 + github.com/libp2p/go-libp2p-kad-dht v0.24.3 github.com/libp2p/go-libp2p-pubsub v0.10.0 github.com/libp2p/go-yamux/v4 v4.0.1 github.com/mattn/go-sqlite3 v1.14.16 @@ -39,9 +42,13 @@ require ( github.com/multiformats/go-multiaddr v0.12.3 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olivere/elastic v6.2.14+incompatible + github.com/prometheus/client_golang v1.18.0 + github.com/prometheus/client_model v0.6.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.4 + go.opencensus.io v0.24.0 + go.uber.org/zap v1.27.0 golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a golang.org/x/sync v0.6.0 @@ -73,24 +80,33 @@ require ( github.com/flynn/noise v1.1.0 // indirect github.com/fortytw2/leaktest v1.3.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.19.5 // 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-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/google/uuid v1.4.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/invopop/yaml v0.1.0 // indirect + github.com/ipfs/boxo v0.10.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect - github.com/ipfs/go-log/v2 v2.5.1 // indirect + github.com/ipfs/go-datastore v0.6.0 // indirect + github.com/ipld/go-ipld-prime v0.20.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect + github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.6 // indirect @@ -100,8 +116,11 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/labstack/gommon v0.4.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-asn-util v0.4.1 // indirect + github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect + github.com/libp2p/go-libp2p-record v0.2.0 // 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 @@ -126,12 +145,12 @@ require ( github.com/multiformats/go-varint v0.0.7 // indirect github.com/onsi/ginkgo/v2 v2.15.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // 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 - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.6.0 // indirect + github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/common v0.47.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -145,16 +164,20 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/fx v1.20.1 // indirect go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect + gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index cdae303d5b..1a52e04711 100644 --- a/go.sum +++ b/go.sum @@ -125,6 +125,7 @@ github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= @@ -140,6 +141,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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= @@ -152,8 +155,11 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev 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= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -164,6 +170,7 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 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= @@ -183,6 +190,9 @@ github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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= @@ -195,7 +205,9 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -206,7 +218,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -218,12 +232,16 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/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= @@ -231,7 +249,14 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 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/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -244,18 +269,34 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY= +github.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM= 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-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= +github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +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-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= +github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= 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/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= +github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= 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-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 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/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 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= @@ -265,6 +306,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= @@ -310,14 +352,22 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 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.33.2 h1:vCdwnFxoGOXMKmaGHlDSnL4bM3fQeW8pgIa9DECnb40= github.com/libp2p/go-libp2p v0.33.2/go.mod h1:zTeppLuCvUIkT118pFVzA8xzP/p2dJYOMApCkFh0Yww= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= +github.com/libp2p/go-libp2p-kad-dht v0.24.3 h1:VjxtDVWaaf4UFjGBf+yl2JCiGaHx7+ctAUa9oJCR3QE= +github.com/libp2p/go-libp2p-kad-dht v0.24.3/go.mod h1:BShPzRbK6+fN3hk8a0WGAYKpb8m4k+DtchkqouGTrSg= +github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= +github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0= github.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA= github.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw= +github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= +github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= @@ -430,6 +480,8 @@ github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 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= @@ -444,6 +496,8 @@ 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/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= +github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= @@ -465,6 +519,7 @@ github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFD github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= 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/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -503,7 +558,11 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic 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/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 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= @@ -531,12 +590,14 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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/cli v1.22.10/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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -547,6 +608,10 @@ github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ 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/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= 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= @@ -560,6 +625,15 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec 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.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 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= @@ -572,9 +646,12 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 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/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= @@ -584,6 +661,7 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf 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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -602,6 +680,7 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/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= @@ -628,6 +707,7 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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= @@ -711,6 +791,9 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -723,6 +806,8 @@ 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= +gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= +gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= 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= @@ -737,6 +822,7 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoA 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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 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= @@ -744,13 +830,18 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -758,6 +849,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= @@ -783,6 +875,7 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh 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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw= diff --git a/installer/config.json.example b/installer/config.json.example index 4a9714115f..7f16155303 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -30,7 +30,7 @@ "ConnectionsRateLimitingWindowSeconds": 1, "CrashDBDir": "", "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)", - "DNSSecurityFlags": 1, + "DNSSecurityFlags": 9, "DeadlockDetection": 0, "DeadlockDetectionThreshold": 30, "DisableAPIAuth": false, @@ -43,6 +43,7 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, + "EnableDHTProviders": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, @@ -53,6 +54,7 @@ "EnableMetricReporting": false, "EnableOutgoingNetworkMessageFiltering": true, "EnableP2P": false, + "EnableP2PHybridMode": false, "EnablePingHandler": true, "EnableProcessBlockStats": false, "EnableProfiler": false, @@ -96,6 +98,7 @@ "OptimizeAccountsDatabaseOnStartup": false, "OutgoingMessageFilterBucketCount": 3, "OutgoingMessageFilterBucketSize": 128, + "P2PNetAddress": "", "P2PPersistPeerID": false, "P2PPrivateKeyLocation": "", "ParticipationKeysRefreshInterval": 60000000000, diff --git a/logging/log.go b/logging/log.go index 48f83c1b9e..770bf08bb9 100644 --- a/logging/log.go +++ b/logging/log.go @@ -148,6 +148,9 @@ type Logger interface { // source adds file, line and function fields to the event source() *logrus.Entry + // Entry returns the logrus raw entry + Entry() *logrus.Entry + // Adds a hook to the logger AddHook(hook logrus.Hook) @@ -316,7 +319,11 @@ func (l logger) getOutput() io.Writer { } func (l logger) SetJSONFormatter() { - l.entry.Logger.Formatter = &logrus.JSONFormatter{TimestampFormat: "2006-01-02T15:04:05.000000Z07:00"} + l.entry.Logger.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02T15:04:05.000000Z07:00"}) +} + +func (l logger) Entry() *logrus.Entry { + return l.entry } func (l logger) source() *logrus.Entry { diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index 2c6e0e423f..4880d76eb9 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -34,6 +34,7 @@ type NodeConfig struct { DashboardEndpoint string `json:",omitempty"` DeadlockOverride int `json:",omitempty"` // -1 = Disable deadlock detection, 0 = Use Default for build, 1 = Enable ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete + P2PBootstrap bool // True if this node should be a p2p bootstrap node and registered in DNS // NodeNameMatchRegex is tested against Name in generated configs and if matched the rest of the configs in this record are applied as a template NodeNameMatchRegex string `json:",omitempty"` diff --git a/netdeploy/remote/nodecfg/nodeConfigurator.go b/netdeploy/remote/nodecfg/nodeConfigurator.go index 5ab43d5ff7..842570bfc8 100644 --- a/netdeploy/remote/nodecfg/nodeConfigurator.go +++ b/netdeploy/remote/nodecfg/nodeConfigurator.go @@ -42,6 +42,7 @@ type nodeConfigurator struct { bootstrappedTrackerDir string relayEndpoints []srvEntry metricsEndpoints []srvEntry + p2pBootstrapEndpoints []txtEntry } type srvEntry struct { @@ -49,6 +50,11 @@ type srvEntry struct { port string } +type txtEntry struct { + netAddress string + peerID string +} + // ApplyConfigurationToHost attempts to apply the provided configuration to the local host, // based on the configuration specified for the provided hostName, with node // directories being created / updated under the specified rootNodeDir @@ -248,6 +254,31 @@ func (nc *nodeConfigurator) registerDNSRecords() (err error) { return } } + + dnsaddrsFrom := fmt.Sprintf("_dnsaddr.%s.algodev.network", nc.genesisData.Network) + for _, entry := range nc.p2pBootstrapEndpoints { + port, parseErr := strconv.ParseInt(strings.Split(entry.netAddress, ":")[1], 10, 64) + if parseErr != nil { + return parseErr + } + var addrType string + if isIP { + addrType = "ip4" + } else { + addrType = "dnsaddr" + } + addrInfoString := fmt.Sprintf("/%s/%s/tcp/%d/p2p/%s", addrType, nc.dnsName, port, entry.peerID) + to := fmt.Sprintf("dnsaddr=%s", addrInfoString) + + fmt.Fprintf(os.Stdout, "...... Adding P2P TXT Record '%s' -> '%s' .\n", dnsaddrsFrom, to) + const priority = 1 + const proxied = false + dnsErr := cloudflareDNS.CreateDNSRecord(context.Background(), "TXT", dnsaddrsFrom, to, cloudflare.AutomaticTTL, priority, proxied) + if dnsErr != nil { + return dnsErr + } + } + return } @@ -281,3 +312,7 @@ func (nc *nodeConfigurator) addRelaySrv(srvRecord string, port string) { func (nc *nodeConfigurator) registerMetricsSrv(srvRecord string, port string) { nc.metricsEndpoints = append(nc.metricsEndpoints, srvEntry{srvRecord, port}) } + +func (nc *nodeConfigurator) addP2PBootstrap(netAddress string, peerID string) { + nc.p2pBootstrapEndpoints = append(nc.p2pBootstrapEndpoints, txtEntry{netAddress, peerID}) +} diff --git a/netdeploy/remote/nodecfg/nodeDir.go b/netdeploy/remote/nodecfg/nodeDir.go index 9bd13343c1..bdfc037438 100644 --- a/netdeploy/remote/nodecfg/nodeDir.go +++ b/netdeploy/remote/nodecfg/nodeDir.go @@ -18,6 +18,7 @@ package nodecfg import ( "encoding/json" + "errors" "fmt" "net/url" "os" @@ -27,6 +28,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/netdeploy/remote" + "github.com/algorand/go-algorand/network/p2p" "github.com/algorand/go-algorand/shared/algoh" "github.com/algorand/go-algorand/util/tokens" ) @@ -101,6 +103,12 @@ func (nd *nodeDir) configure() (err error) { fmt.Fprintf(os.Stdout, "Error during configureNetAddress: %s\n", err) return } + + if err = nd.configureP2PDNSBootstrap(nd.P2PBootstrap); err != nil { + fmt.Fprintf(os.Stdout, "Error during configureP2PDNSBootstrap: %s\n", err) + return + } + fmt.Println("Done configuring node directory.") return } @@ -156,6 +164,45 @@ func (nd *nodeDir) configureNetAddress() (err error) { return } +func (nd *nodeDir) configureP2PDNSBootstrap(p2pBootstrap bool) error { + if !p2pBootstrap { + return nil + } + fmt.Fprintf(os.Stdout, " - Configuring P2P DNS Bootstrap: %s\n", nd.Name) + if err := nd.ensureConfig(); err != nil { + return err + } + // ensure p2p config params set are what is expected: + // - EnableP2P or EnableP2PHybridMode + // - NetAddress or P2PNetAddress is set + // - EnableGossipService + if !nd.config.EnableP2P && !nd.config.EnableP2PHybridMode { + return errors.New("p2p bootstrap requires EnableP2P or EnableP2PHybridMode to be set") + } + if nd.NetAddress == "" && nd.config.P2PNetAddress == "" { + return errors.New("p2p bootstrap requires NetAddress or P2PNetAddress to be set") + } + if !nd.config.EnableGossipService { + return errors.New("p2p bootstrap requires EnableGossipService to be set") + } + + netAddress := nd.NetAddress + if nd.config.P2PNetAddress != "" { + netAddress = nd.config.P2PNetAddress + } + + key, err := p2p.GetPrivKey(config.Local{P2PPersistPeerID: true}, nd.dataDir) + if err != nil { + return err + } + peerID, err := p2p.PeerIDFromPublicKey(key.GetPublic()) + if err != nil { + return err + } + nd.configurator.addP2PBootstrap(netAddress, peerID.String()) + return nil +} + func (nd *nodeDir) configureAPIEndpoint(address string) (err error) { if err = nd.ensureConfig(); err != nil { return diff --git a/network/addr.go b/network/addr.go index 1e2b04a447..00eb368881 100644 --- a/network/addr.go +++ b/network/addr.go @@ -17,73 +17,17 @@ package network import ( - "errors" - "net/url" "path" - "regexp" "strings" - "github.com/multiformats/go-multiaddr" + "github.com/algorand/go-algorand/network/addr" ) -var errURLNoHost = errors.New("could not parse a host from url") - -var errURLColonHost = errors.New("host name starts with a colon") - -// HostColonPortPattern matches "^[-a-zA-Z0-9.]+:\\d+$" e.g. "foo.com.:1234" -var HostColonPortPattern = regexp.MustCompile(`^[-a-zA-Z0-9.]+:\d+$`) - -// ParseHostOrURL handles "host:port" or a full URL. -// Standard library net/url.Parse chokes on "host:port". -func ParseHostOrURL(addr string) (*url.URL, error) { - // If the entire addr is "host:port" grab that right away. - // Don't try url.Parse() because that will grab "host:" as if it were "scheme:" - if HostColonPortPattern.MatchString(addr) { - return &url.URL{Scheme: "http", Host: addr}, nil - } - parsed, err := url.Parse(addr) - if err == nil { - if parsed.Host == "" { - return nil, errURLNoHost - } - return parsed, nil - } - if strings.HasPrefix(addr, "http:") || strings.HasPrefix(addr, "https:") || strings.HasPrefix(addr, "ws:") || strings.HasPrefix(addr, "wss:") || strings.HasPrefix(addr, "://") || strings.HasPrefix(addr, "//") { - return parsed, err - } - // This turns "[::]:4601" into "http://[::]:4601" which url.Parse can do - parsed, e2 := url.Parse("http://" + addr) - if e2 == nil { - // https://datatracker.ietf.org/doc/html/rfc1123#section-2 - // first character is relaxed to allow either a letter or a digit - if parsed.Host[0] == ':' && (len(parsed.Host) < 2 || parsed.Host[1] != ':') { - return nil, errURLColonHost - } - return parsed, nil - } - return parsed, err /* return original err, not our prefix altered try */ -} - -// ParseHostOrURLOrMultiaddr returns an error if it could not parse the provided -// string as a valid "host:port", full URL, or multiaddr. If no error, it returns -// a host:port address, or a multiaddr. -func ParseHostOrURLOrMultiaddr(addr string) (string, error) { - if strings.HasPrefix(addr, "/") && !strings.HasPrefix(addr, "//") { // multiaddr starts with '/' but not '//' which is possible for scheme relative URLS - _, err := multiaddr.NewMultiaddr(addr) - return addr, err - } - url, err := ParseHostOrURL(addr) - if err != nil { - return "", err - } - return url.Host, nil -} - // addrToGossipAddr parses host:port or a URL and returns the URL to the websocket interface at that address. -func (wn *WebsocketNetwork) addrToGossipAddr(addr string) (string, error) { - parsedURL, err := ParseHostOrURL(addr) +func (wn *WebsocketNetwork) addrToGossipAddr(a string) (string, error) { + parsedURL, err := addr.ParseHostOrURL(a) if err != nil { - wn.log.Warnf("could not parse addr %#v: %s", addr, err) + wn.log.Warnf("could not parse addr %#v: %s", a, err) return "", errBadAddr } parsedURL.Scheme = websocketsScheme[parsedURL.Scheme] diff --git a/network/addr/addr.go b/network/addr/addr.go new file mode 100644 index 0000000000..c8c0c0b6ab --- /dev/null +++ b/network/addr/addr.go @@ -0,0 +1,88 @@ +// Copyright (C) 2019-2024 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 addr + +import ( + "errors" + "net/url" + "regexp" + "strings" + + "github.com/multiformats/go-multiaddr" +) + +var errURLNoHost = errors.New("could not parse a host from url") + +var errURLColonHost = errors.New("host name starts with a colon") + +// HostColonPortPattern matches "^[-a-zA-Z0-9.]+:\\d+$" e.g. "foo.com.:1234" +var HostColonPortPattern = regexp.MustCompile(`^[-a-zA-Z0-9.]+:\d+$`) + +// ParseHostOrURL handles "host:port" or a full URL. +// Standard library net/url.Parse chokes on "host:port". +func ParseHostOrURL(addr string) (*url.URL, error) { + // If the entire addr is "host:port" grab that right away. + // Don't try url.Parse() because that will grab "host:" as if it were "scheme:" + if HostColonPortPattern.MatchString(addr) { + return &url.URL{Scheme: "http", Host: addr}, nil + } + parsed, err := url.Parse(addr) + if err == nil { + if parsed.Host == "" { + return nil, errURLNoHost + } + return parsed, nil + } + if strings.HasPrefix(addr, "http:") || strings.HasPrefix(addr, "https:") || strings.HasPrefix(addr, "ws:") || strings.HasPrefix(addr, "wss:") || strings.HasPrefix(addr, "://") || strings.HasPrefix(addr, "//") { + return parsed, err + } + // This turns "[::]:4601" into "http://[::]:4601" which url.Parse can do + parsed, e2 := url.Parse("http://" + addr) + if e2 == nil { + // https://datatracker.ietf.org/doc/html/rfc1123#section-2 + // first character is relaxed to allow either a letter or a digit + if parsed.Host[0] == ':' && (len(parsed.Host) < 2 || parsed.Host[1] != ':') { + return nil, errURLColonHost + } + return parsed, nil + } + return parsed, err /* return original err, not our prefix altered try */ +} + +// IsMultiaddr returns true if the provided string is a valid multiaddr. +func IsMultiaddr(addr string) bool { + if strings.HasPrefix(addr, "/") && !strings.HasPrefix(addr, "//") { // multiaddr starts with '/' but not '//' which is possible for scheme relative URLS + _, err := multiaddr.NewMultiaddr(addr) + return err == nil + } + return false +} + +// ParseHostOrURLOrMultiaddr returns an error if it could not parse the provided +// string as a valid "host:port", full URL, or multiaddr. If no error, it returns +// a host:port address, or a multiaddr. +func ParseHostOrURLOrMultiaddr(addr string) (string, error) { + if strings.HasPrefix(addr, "/") && !strings.HasPrefix(addr, "//") { // multiaddr starts with '/' but not '//' which is possible for scheme relative URLS + _, err := multiaddr.NewMultiaddr(addr) + return addr, err + } + url, err := ParseHostOrURL(addr) + if err != nil { + return "", err + } + return url.Host, nil +} diff --git a/network/addr_test.go b/network/addr/addr_test.go similarity index 95% rename from network/addr_test.go rename to network/addr/addr_test.go index 377fe72a91..bceeb079f2 100644 --- a/network/addr_test.go +++ b/network/addr/addr_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package addr import ( "net/url" @@ -31,6 +31,8 @@ type urlCase struct { func TestParseHostOrURL(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() + urlTestCases := []urlCase{ {"localhost:123", url.URL{Scheme: "http", Host: "localhost:123"}}, {"http://localhost:123", url.URL{Scheme: "http", Host: "localhost:123"}}, @@ -89,10 +91,12 @@ func TestParseHostOrURL(t *testing.T) { t.Run(addr, func(t *testing.T) { _, err := ParseHostOrURL(addr) require.Error(t, err, "url should fail", addr) + require.False(t, IsMultiaddr(addr)) }) t.Run(addr+"-multiaddr", func(t *testing.T) { _, err := ParseHostOrURLOrMultiaddr(addr) require.Error(t, err, "url should fail", addr) + require.False(t, IsMultiaddr(addr)) }) } @@ -100,6 +104,7 @@ func TestParseHostOrURL(t *testing.T) { func TestParseHostURLOrMultiaddr(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() validMultiAddrs := []string{ "/ip4/127.0.0.1/tcp/8080", @@ -124,6 +129,7 @@ func TestParseHostURLOrMultiaddr(t *testing.T) { v, err := ParseHostOrURLOrMultiaddr(addr) require.NoError(t, err) require.Equal(t, addr, v) + require.True(t, IsMultiaddr(addr)) }) } @@ -131,6 +137,7 @@ func TestParseHostURLOrMultiaddr(t *testing.T) { t.Run(addr, func(t *testing.T) { _, err := ParseHostOrURLOrMultiaddr(addr) require.Error(t, err) + require.False(t, IsMultiaddr(addr)) }) } diff --git a/network/connPerfMon_test.go b/network/connPerfMon_test.go index b2f24f1214..560be72a96 100644 --- a/network/connPerfMon_test.go +++ b/network/connPerfMon_test.go @@ -48,7 +48,7 @@ func makeMsgPool(N int, peers []Peer) (out []IncomingMessage) { addMsg := func(msgCount int) { for i := 0; i < msgCount; i++ { - msg.Sender = peers[(int(msgIndex)+i)%len(peers)] + msg.Sender = peers[(int(msgIndex)+i)%len(peers)].(DisconnectablePeer) timer += int64(7 * time.Nanosecond) msg.Received = timer out = append(out, msg) diff --git a/network/gossipNode.go b/network/gossipNode.go index 3ac5cc7df0..6a028ff193 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -18,8 +18,9 @@ package network import ( "context" - "net" "net/http" + "strings" + "time" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/protocol" @@ -28,6 +29,11 @@ import ( // Peer opaque interface for referring to a neighbor in the network type Peer interface{} +// DisconnectablePeer is a Peer with a long-living connection to a network that can be disconnected +type DisconnectablePeer interface { + GetNetwork() GossipNode +} + // PeerOption allows users to specify a subset of peers to query // //msgp:ignore PeerOption @@ -44,12 +50,19 @@ const ( PeersPhonebookArchivalNodes PeerOption = iota ) +// DeadlineSettableConn abstracts net.Conn and related types as deadline-settable +type DeadlineSettableConn interface { + SetDeadline(time.Time) error + SetReadDeadline(time.Time) error + SetWriteDeadline(time.Time) error +} + // GossipNode represents a node in the gossip network type GossipNode interface { Address() (string, bool) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error Relay(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error - Disconnect(badnode Peer) + Disconnect(badnode DisconnectablePeer) DisconnectPeers() // only used by testing // RegisterHTTPHandler path accepts gorilla/mux path annotations @@ -64,7 +77,7 @@ type GossipNode interface { GetPeers(options ...PeerOption) []Peer // Start threads, listen on sockets. - Start() + Start() error // Close sockets. Stop threads. Stop() @@ -75,8 +88,15 @@ type GossipNode interface { // ClearHandlers deregisters all the existing message handlers. ClearHandlers() - // GetRoundTripper returns a Transport that would limit the number of outgoing connections. - GetRoundTripper() http.RoundTripper + // RegisterProcessors adds to the set of given message processors. + RegisterProcessors(dispatch []TaggedMessageProcessor) + + // ClearProcessors deregisters all the existing message processors. + ClearProcessors() + + // GetHTTPClient returns a http.Client with a suitable for the network Transport + // that would also limit the number of outgoing connections. + GetHTTPClient(address string) (*http.Client, error) // OnNetworkAdvance notifies the network library that the agreement protocol was able to make a notable progress. // this is the only indication that we have that we haven't formed a clique, where all incoming messages @@ -86,10 +106,10 @@ type GossipNode interface { // GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same // request that was provided to the http handler ( or provide a fallback Context() to that ) - GetHTTPRequestConnection(request *http.Request) (conn net.Conn) + GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) - // SubstituteGenesisID substitutes the "{genesisID}" with their network-specific genesisID. - SubstituteGenesisID(rawURL string) string + // GetGenesisID returns the network-specific genesisID. + GetGenesisID() string // called from wsPeer to report that it has closed peerRemoteClose(peer *wsPeer, reason disconnectReason) @@ -107,7 +127,7 @@ var outgoingMessagesBufferSize = int( // IncomingMessage represents a message arriving from some peer in our p2p network type IncomingMessage struct { - Sender Peer + Sender DisconnectablePeer Tag Tag Data []byte Err error @@ -148,6 +168,14 @@ type OutgoingMessage struct { OnRelease func() } +// ValidatedMessage is a message that has been validated and is ready to be processed. +// Think as an intermediate one between IncomingMessage and OutgoingMessage +type ValidatedMessage struct { + Action ForwardingPolicy + Tag Tag + ValidatedMessage interface{} +} + // ForwardingPolicy is an enum indicating to whom we should send a message // //msgp:ignore ForwardingPolicy @@ -165,6 +193,9 @@ const ( // Respond - reply to the sender Respond + + // Accept - accept for further processing after successful validation + Accept ) // MessageHandler takes a IncomingMessage (e.g., vote, transaction), processes it, and returns what (if anything) @@ -175,20 +206,51 @@ type MessageHandler interface { Handle(message IncomingMessage) OutgoingMessage } -// HandlerFunc represents an implemenation of the MessageHandler interface +// HandlerFunc represents an implementation of the MessageHandler interface type HandlerFunc func(message IncomingMessage) OutgoingMessage -// Handle implements MessageHandler.Handle, calling the handler with the IncomingKessage and returning the OutgoingMessage +// Handle implements MessageHandler.Handle, calling the handler with the IncomingMessage and returning the OutgoingMessage func (f HandlerFunc) Handle(message IncomingMessage) OutgoingMessage { return f(message) } -// TaggedMessageHandler receives one type of broadcast messages -type TaggedMessageHandler struct { +// MessageProcessor takes a IncomingMessage (e.g., vote, transaction), processes it, and returns what (if anything) +// to send to the network in response. +// This is an extension of the MessageHandler that works in two stages: validate ->[result]-> handle. +type MessageProcessor interface { + Validate(message IncomingMessage) ValidatedMessage + Handle(message ValidatedMessage) OutgoingMessage +} + +// ProcessorValidateFunc represents an implementation of the MessageProcessor interface +type ProcessorValidateFunc func(message IncomingMessage) ValidatedMessage + +// ProcessorHandleFunc represents an implementation of the MessageProcessor interface +type ProcessorHandleFunc func(message ValidatedMessage) OutgoingMessage + +// Validate implements MessageProcessor.Validate, calling the validator with the IncomingMessage and returning the action +// and validation extra data that can be use as the handler input. +func (f ProcessorValidateFunc) Validate(message IncomingMessage) ValidatedMessage { + return f(message) +} + +// Handle implements MessageProcessor.Handle calling the handler with the ValidatedMessage and returning the OutgoingMessage +func (f ProcessorHandleFunc) Handle(message ValidatedMessage) OutgoingMessage { + return f(message) +} + +type taggedMessageDispatcher[T any] struct { Tag - MessageHandler + MessageHandler T } +// TaggedMessageHandler receives one type of broadcast messages +type TaggedMessageHandler = taggedMessageDispatcher[MessageHandler] + +// TaggedMessageProcessor receives one type of broadcast messages +// and performs two stage processing: validating and handling +type TaggedMessageProcessor = taggedMessageDispatcher[MessageProcessor] + // Propagate is a convenience function to save typing in the common case of a message handler telling us to propagate an incoming message // "return network.Propagate(msg)" instead of "return network.OutgoingMsg{network.Broadcast, msg.Tag, msg.Data}" func Propagate(msg IncomingMessage) OutgoingMessage { @@ -205,3 +267,8 @@ func max(numbers ...uint64) (maxNum uint64) { } return } + +// SubstituteGenesisID substitutes the "{genesisID}" with their network-specific genesisID. +func SubstituteGenesisID(net GossipNode, rawURL string) string { + return strings.Replace(rawURL, "{genesisID}", net.GetGenesisID(), -1) +} diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go new file mode 100644 index 0000000000..f324deb73f --- /dev/null +++ b/network/hybridNetwork.go @@ -0,0 +1,233 @@ +// Copyright (C) 2019-2024 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 network + +import ( + "context" + "fmt" + "net/http" + "sync" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/addr" + "github.com/algorand/go-algorand/protocol" +) + +// HybridP2PNetwork runs both P2PNetwork and WebsocketNetwork to implement the GossipNode interface +type HybridP2PNetwork struct { + p2pNetwork *P2PNetwork + wsNetwork *WebsocketNetwork + genesisID string + + useP2PAddress bool +} + +// NewHybridP2PNetwork constructs a GossipNode that combines P2PNetwork and WebsocketNetwork +func NewHybridP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo) (*HybridP2PNetwork, error) { + // supply alternate NetAddress for P2P network + p2pcfg := cfg + p2pcfg.NetAddress = cfg.P2PNetAddress + p2pnet, err := NewP2PNetwork(log, p2pcfg, datadir, phonebookAddresses, genesisID, networkID, nodeInfo) + if err != nil { + return nil, err + } + wsnet, err := NewWebsocketNetwork(log, cfg, phonebookAddresses, genesisID, networkID, nodeInfo, p2pnet.PeerID(), p2pnet.PeerIDSigner()) + if err != nil { + return nil, err + } + return &HybridP2PNetwork{ + p2pNetwork: p2pnet, + wsNetwork: wsnet, + genesisID: genesisID, + }, nil +} + +// Address implements GossipNode +func (n *HybridP2PNetwork) Address() (string, bool) { + // TODO map from configuration? used for REST API, goal status, algod.net, etc + if n.useP2PAddress { + return n.p2pNetwork.Address() + } + return n.wsNetwork.Address() +} + +type hybridNetworkError struct{ p2pErr, wsErr error } + +func (e *hybridNetworkError) Error() string { + return fmt.Sprintf("p2pErr: %s, wsErr: %s", e.p2pErr, e.wsErr) +} +func (e *hybridNetworkError) Unwrap() []error { return []error{e.p2pErr, e.wsErr} } + +func (n *HybridP2PNetwork) runParallel(fn func(net GossipNode) error) error { + var wg sync.WaitGroup + var p2pErr, wsErr error + + wg.Add(2) + go func() { + defer wg.Done() + p2pErr = fn(n.p2pNetwork) + }() + go func() { + defer wg.Done() + wsErr = fn(n.wsNetwork) + }() + wg.Wait() + + if p2pErr != nil && wsErr != nil { + return &hybridNetworkError{p2pErr, wsErr} + } + if p2pErr != nil { + return p2pErr + } + if wsErr != nil { + return wsErr + } + return nil +} + +// Broadcast implements GossipNode +func (n *HybridP2PNetwork) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error { + return n.runParallel(func(net GossipNode) error { + return net.Broadcast(ctx, tag, data, wait, except) + }) +} + +// Relay implements GossipNode +func (n *HybridP2PNetwork) Relay(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error { + return n.runParallel(func(net GossipNode) error { + return net.Relay(ctx, tag, data, wait, except) + }) +} + +// Disconnect implements GossipNode +func (n *HybridP2PNetwork) Disconnect(badnode DisconnectablePeer) { + net := badnode.GetNetwork() + if net == n.p2pNetwork { + n.p2pNetwork.Disconnect(badnode) + } else if net == n.wsNetwork { + n.wsNetwork.Disconnect(badnode) + } else { + panic("badnode.GetNetwork() returned a network that is not part of this HybridP2PNetwork") + } +} + +// DisconnectPeers implements GossipNode +func (n *HybridP2PNetwork) DisconnectPeers() { + _ = n.runParallel(func(net GossipNode) error { + net.DisconnectPeers() + return nil + }) +} + +// RegisterHTTPHandler implements GossipNode +func (n *HybridP2PNetwork) RegisterHTTPHandler(path string, handler http.Handler) { + n.p2pNetwork.RegisterHTTPHandler(path, handler) + n.wsNetwork.RegisterHTTPHandler(path, handler) +} + +// RequestConnectOutgoing implements GossipNode +func (n *HybridP2PNetwork) RequestConnectOutgoing(replace bool, quit <-chan struct{}) {} + +// GetPeers implements GossipNode +func (n *HybridP2PNetwork) GetPeers(options ...PeerOption) []Peer { + // TODO better way of combining data from peerstore and returning in GetPeers + var peers []Peer + peers = append(peers, n.p2pNetwork.GetPeers(options...)...) + peers = append(peers, n.wsNetwork.GetPeers(options...)...) + return peers +} + +// Start implements GossipNode +func (n *HybridP2PNetwork) Start() error { + err := n.runParallel(func(net GossipNode) error { + return net.Start() + }) + return err +} + +// Stop implements GossipNode +func (n *HybridP2PNetwork) Stop() { + _ = n.runParallel(func(net GossipNode) error { + net.Stop() + return nil + }) +} + +// RegisterHandlers adds to the set of given message handlers. +func (n *HybridP2PNetwork) RegisterHandlers(dispatch []TaggedMessageHandler) { + n.p2pNetwork.RegisterHandlers(dispatch) + n.wsNetwork.RegisterHandlers(dispatch) +} + +// ClearHandlers deregisters all the existing message handlers. +func (n *HybridP2PNetwork) ClearHandlers() { + n.p2pNetwork.ClearHandlers() + n.wsNetwork.ClearHandlers() +} + +// RegisterProcessors adds to the set of given message processors. +func (n *HybridP2PNetwork) RegisterProcessors(dispatch []TaggedMessageProcessor) { + n.p2pNetwork.RegisterProcessors(dispatch) + n.wsNetwork.RegisterProcessors(dispatch) +} + +// ClearProcessors deregisters all the existing message processors. +func (n *HybridP2PNetwork) ClearProcessors() { + n.p2pNetwork.ClearProcessors() + n.wsNetwork.ClearProcessors() +} + +// GetHTTPClient returns a http.Client with a suitable for the network Transport +// that would also limit the number of outgoing connections. +func (n *HybridP2PNetwork) GetHTTPClient(address string) (*http.Client, error) { + if addr.IsMultiaddr(address) { + return n.p2pNetwork.GetHTTPClient(address) + } + return n.wsNetwork.GetHTTPClient(address) +} + +// OnNetworkAdvance notifies the network library that the agreement protocol was able to make a notable progress. +// this is the only indication that we have that we haven't formed a clique, where all incoming messages +// arrive very quickly, but might be missing some votes. The usage of this call is expected to have similar +// characteristics as with a watchdog timer. +func (n *HybridP2PNetwork) OnNetworkAdvance() { + _ = n.runParallel(func(net GossipNode) error { + net.OnNetworkAdvance() + return nil + }) +} + +// GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same +// request that was provided to the http handler ( or provide a fallback Context() to that ) +func (n *HybridP2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) { + conn = n.wsNetwork.GetHTTPRequestConnection(request) + if conn != nil { + return conn + } + return n.p2pNetwork.GetHTTPRequestConnection(request) +} + +// GetGenesisID returns the network-specific genesisID. +func (n *HybridP2PNetwork) GetGenesisID() string { + return n.genesisID +} + +// called from wsPeer to report that it has closed +func (n *HybridP2PNetwork) peerRemoteClose(peer *wsPeer, reason disconnectReason) { + panic("wsPeer should only call WebsocketNetwork.peerRemoteClose or P2PNetwork.peerRemoteClose") +} diff --git a/network/dialer.go b/network/limitcaller/dialer.go similarity index 91% rename from network/dialer.go rename to network/limitcaller/dialer.go index 3aa59f493d..ee9b2e364a 100644 --- a/network/dialer.go +++ b/network/limitcaller/dialer.go @@ -14,13 +14,14 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package limitcaller import ( "context" "net" "time" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/tools/network/dnssec" "github.com/algorand/go-algorand/util" ) @@ -31,14 +32,13 @@ type netDialer interface { // Dialer establish tcp-level connection with the destination type Dialer struct { - phonebook Phonebook + phonebook phonebook.Phonebook innerDialer netDialer - resolver *net.Resolver } -// makeRateLimitingDialer creates a rate limiting dialer that would limit the connections +// MakeRateLimitingDialer creates a rate limiting dialer that would limit the connections // according to the entries in the phonebook. -func makeRateLimitingDialer(phonebook Phonebook, resolver dnssec.ResolverIf) Dialer { +func MakeRateLimitingDialer(phonebook phonebook.Phonebook, resolver dnssec.ResolverIf) Dialer { var innerDialer netDialer = &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, diff --git a/network/rateLimitingTransport.go b/network/limitcaller/rateLimitingTransport.go similarity index 55% rename from network/rateLimitingTransport.go rename to network/limitcaller/rateLimitingTransport.go index 461a468da5..45bc0725ed 100644 --- a/network/rateLimitingTransport.go +++ b/network/limitcaller/rateLimitingTransport.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package limitcaller import ( "errors" @@ -22,24 +22,35 @@ import ( "time" "github.com/algorand/go-algorand/util" + "github.com/libp2p/go-libp2p/core/peer" ) -// rateLimitingTransport is the transport for execute a single HTTP transaction, obtaining the Response for a given Request. -type rateLimitingTransport struct { - phonebook Phonebook - innerTransport *http.Transport +// ConnectionTimeStore is a subset of the phonebook that is used to store the connection times. +type ConnectionTimeStore interface { + GetConnectionWaitTime(addrOrPeerID string) (bool, time.Duration, time.Time) + UpdateConnectionTime(addrOrPeerID string, provisionalTime time.Time) bool +} + +// RateLimitingTransport is the transport for execute a single HTTP transaction, obtaining the Response for a given Request. +type RateLimitingTransport struct { + phonebook ConnectionTimeStore + innerTransport http.RoundTripper queueingTimeout time.Duration + targetAddr interface{} // target address for the p2p http request } +// DefaultQueueingTimeout is the default timeout for queueing the request. +const DefaultQueueingTimeout = 10 * time.Second + // ErrConnectionQueueingTimeout indicates that we've exceeded the time allocated for // queueing the current request before the request attempt could be made. var ErrConnectionQueueingTimeout = errors.New("rateLimitingTransport: queueing timeout") -// makeRateLimitingTransport creates a rate limiting http transport that would limit the requests rate +// MakeRateLimitingTransport creates a rate limiting http transport that would limit the requests rate // according to the entries in the phonebook. -func makeRateLimitingTransport(phonebook Phonebook, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int) rateLimitingTransport { +func MakeRateLimitingTransport(phonebook ConnectionTimeStore, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int) RateLimitingTransport { defaultTransport := http.DefaultTransport.(*http.Transport) - return rateLimitingTransport{ + return RateLimitingTransport{ phonebook: phonebook, innerTransport: &http.Transport{ Proxy: defaultTransport.Proxy, @@ -54,14 +65,34 @@ func makeRateLimitingTransport(phonebook Phonebook, queueingTimeout time.Duratio } } +// MakeRateLimitingTransportWithRoundTripper creates a rate limiting http transport that would limit the requests rate +// according to the entries in the phonebook. +func MakeRateLimitingTransportWithRoundTripper(phonebook ConnectionTimeStore, queueingTimeout time.Duration, rt http.RoundTripper, target interface{}, maxIdleConnsPerHost int) RateLimitingTransport { + return RateLimitingTransport{ + phonebook: phonebook, + innerTransport: rt, + queueingTimeout: queueingTimeout, + targetAddr: target, + } +} + // RoundTrip connects to the address on the named network using the provided context. // It waits if needed not to exceed connectionsRateLimitingCount. -func (r *rateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response, err error) { +func (r *RateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response, err error) { var waitTime time.Duration var provisionalTime time.Time queueingDeadline := time.Now().Add(r.queueingTimeout) + addrOrPeerID := req.Host + // p2p/http clients have per-connection transport and address info so use that + if len(req.Host) == 0 && req.URL != nil && len(req.URL.Host) == 0 { + addrInfo, ok := r.targetAddr.(*peer.AddrInfo) + if !ok { + return nil, errors.New("rateLimitingTransport: request without Host/URL and targetAddr is not a peer.AddrInfo") + } + addrOrPeerID = string(addrInfo.ID) + } for { - _, waitTime, provisionalTime = r.phonebook.GetConnectionWaitTime(req.Host) + _, waitTime, provisionalTime = r.phonebook.GetConnectionWaitTime(addrOrPeerID) if waitTime == 0 { break // break out of the loop and proceed to the connection } @@ -73,6 +104,6 @@ func (r *rateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response return nil, ErrConnectionQueueingTimeout } res, err = r.innerTransport.RoundTrip(req) - r.phonebook.UpdateConnectionTime(req.Host, provisionalTime) + r.phonebook.UpdateConnectionTime(addrOrPeerID, provisionalTime) return } diff --git a/network/multiplexer.go b/network/multiplexer.go index 0e97d63f28..dc38fba277 100644 --- a/network/multiplexer.go +++ b/network/multiplexer.go @@ -24,49 +24,74 @@ import ( // Multiplexer is a message handler that sorts incoming messages by Tag and passes // them along to the relevant message handler for that type of message. type Multiplexer struct { - msgHandlers atomic.Value // stores map[Tag]MessageHandler, an immutable map. + msgHandlers atomic.Value // stores map[Tag]MessageHandler, an immutable map. + msgProcessors atomic.Value // stores map[Tag]MessageProcessor, an immutable map. } // MakeMultiplexer creates an empty Multiplexer func MakeMultiplexer() *Multiplexer { m := &Multiplexer{} - m.ClearHandlers([]Tag{}) // allocate the map + m.ClearHandlers(nil) // allocate the map + m.ClearProcessors(nil) // allocate the map return m } -// getHandlersMap retrieves the handlers map. -func (m *Multiplexer) getHandlersMap() map[Tag]MessageHandler { - handlersVal := m.msgHandlers.Load() - if handlers, valid := handlersVal.(map[Tag]MessageHandler); valid { +// getMap retrieves a typed map from an atomic.Value. +func getMap[T any](source *atomic.Value) map[Tag]T { + mp := source.Load() + if handlers, valid := mp.(map[Tag]T); valid { return handlers } return nil } -// Retrives the handler for the given message Tag from the handlers array while taking a read lock. -func (m *Multiplexer) getHandler(tag Tag) (MessageHandler, bool) { - if handlers := m.getHandlersMap(); handlers != nil { +// Retrieves the handler for the given message Tag from the given value while. +func getHandler[T any](source *atomic.Value, tag Tag) (T, bool) { + if handlers := getMap[T](source); handlers != nil { handler, ok := handlers[tag] return handler, ok } - return nil, false + var empty T + return empty, false +} + +// Retrieves the handler for the given message Tag from the handlers array. +func (m *Multiplexer) getHandler(tag Tag) (MessageHandler, bool) { + return getHandler[MessageHandler](&m.msgHandlers, tag) +} + +// Retrieves the processor for the given message Tag from the processors array. +func (m *Multiplexer) getProcessor(tag Tag) (MessageProcessor, bool) { + return getHandler[MessageProcessor](&m.msgProcessors, tag) } // Handle is the "input" side of the multiplexer. It dispatches the message to the previously defined handler. func (m *Multiplexer) Handle(msg IncomingMessage) OutgoingMessage { - handler, ok := m.getHandler(msg.Tag) + if handler, ok := m.getHandler(msg.Tag); ok { + return handler.Handle(msg) + } + return OutgoingMessage{} +} - if ok { - outmsg := handler.Handle(msg) - return outmsg +// Validate is an alternative "input" side of the multiplexer. It dispatches the message to the previously defined validator. +func (m *Multiplexer) Validate(msg IncomingMessage) ValidatedMessage { + if handler, ok := m.getProcessor(msg.Tag); ok { + return handler.Validate(msg) + } + return ValidatedMessage{} +} + +// Process is the second step of message handling after validation. It dispatches the message to the previously defined processor. +func (m *Multiplexer) Process(msg ValidatedMessage) OutgoingMessage { + if handler, ok := m.getProcessor(msg.Tag); ok { + return handler.Handle(msg) } return OutgoingMessage{} } -// RegisterHandlers registers the set of given message handlers. -func (m *Multiplexer) RegisterHandlers(dispatch []TaggedMessageHandler) { - mp := make(map[Tag]MessageHandler) - if existingMap := m.getHandlersMap(); existingMap != nil { +func registerMultiplexer[T any](target *atomic.Value, dispatch []taggedMessageDispatcher[T]) { + mp := make(map[Tag]T) + if existingMap := getMap[T](target); existingMap != nil { for k, v := range existingMap { mp[k] = v } @@ -77,13 +102,22 @@ func (m *Multiplexer) RegisterHandlers(dispatch []TaggedMessageHandler) { } mp[v.Tag] = v.MessageHandler } - m.msgHandlers.Store(mp) + target.Store(mp) } -// ClearHandlers deregisters all the existing message handlers other than the one provided in the excludeTags list -func (m *Multiplexer) ClearHandlers(excludeTags []Tag) { +// RegisterHandlers registers the set of given message handlers. +func (m *Multiplexer) RegisterHandlers(dispatch []TaggedMessageHandler) { + registerMultiplexer(&m.msgHandlers, dispatch) +} + +// RegisterProcessors registers the set of given message handlers. +func (m *Multiplexer) RegisterProcessors(dispatch []TaggedMessageProcessor) { + registerMultiplexer(&m.msgProcessors, dispatch) +} + +func clearMultiplexer[T any](target *atomic.Value, excludeTags []Tag) { if len(excludeTags) == 0 { - m.msgHandlers.Store(make(map[Tag]MessageHandler)) + target.Store(make(map[Tag]T)) return } @@ -93,13 +127,23 @@ func (m *Multiplexer) ClearHandlers(excludeTags []Tag) { excludeTagsMap[tag] = true } - currentHandlersMap := m.getHandlersMap() - newMap := make(map[Tag]MessageHandler, len(excludeTagsMap)) - for tag, handler := range currentHandlersMap { + currentMap := getMap[T](target) + newMap := make(map[Tag]T, len(excludeTagsMap)) + for tag, handler := range currentMap { if excludeTagsMap[tag] { newMap[tag] = handler } } - m.msgHandlers.Store(newMap) + target.Store(newMap) +} + +// ClearHandlers deregisters all the existing message handlers other than the one provided in the excludeTags list +func (m *Multiplexer) ClearHandlers(excludeTags []Tag) { + clearMultiplexer[MessageHandler](&m.msgHandlers, excludeTags) +} + +// ClearProcessors deregisters all the existing message handlers other than the one provided in the excludeTags list +func (m *Multiplexer) ClearProcessors(excludeTags []Tag) { + clearMultiplexer[MessageProcessor](&m.msgProcessors, excludeTags) } diff --git a/network/netidentity.go b/network/netidentity.go index 1474739bd1..4d797a1a5b 100644 --- a/network/netidentity.go +++ b/network/netidentity.go @@ -94,12 +94,34 @@ type identityChallengeScheme interface { VerifyResponse(h http.Header, c identityChallengeValue) (crypto.PublicKey, []byte, error) } +type identityChallengeSigner interface { + Sign(message crypto.Hashable) crypto.Signature + SignBytes(message []byte) crypto.Signature + PublicKey() crypto.PublicKey +} + +type identityChallengeLegacySigner struct { + keys *crypto.SignatureSecrets +} + +func (s *identityChallengeLegacySigner) Sign(message crypto.Hashable) crypto.Signature { + return s.keys.Sign(message) +} + +func (s *identityChallengeLegacySigner) SignBytes(message []byte) crypto.Signature { + return s.keys.SignBytes(message) +} + +func (s *identityChallengeLegacySigner) PublicKey() crypto.PublicKey { + return s.keys.SignatureVerifier +} + // identityChallengePublicKeyScheme implements IdentityChallengeScheme by // exchanging and verifying public key challenges and attaching them to headers, // or returning the message payload to be sent type identityChallengePublicKeyScheme struct { dedupName string - identityKeys *crypto.SignatureSecrets + identityKeys identityChallengeSigner } // NewIdentityChallengeScheme will create a default Identification Scheme @@ -108,15 +130,21 @@ func NewIdentityChallengeScheme(dn string) *identityChallengePublicKeyScheme { if dn == "" { return &identityChallengePublicKeyScheme{} } + var seed crypto.Seed crypto.RandBytes(seed[:]) return &identityChallengePublicKeyScheme{ dedupName: dn, - identityKeys: crypto.GenerateSignatureSecrets(seed), + identityKeys: &identityChallengeLegacySigner{keys: crypto.GenerateSignatureSecrets(seed)}, } } +// NewIdentityChallengeSchemeWithSigner will create an identification Scheme with a given signer +func NewIdentityChallengeSchemeWithSigner(dn string, signer identityChallengeSigner) *identityChallengePublicKeyScheme { + return &identityChallengePublicKeyScheme{dedupName: dn, identityKeys: signer} +} + // AttachChallenge will generate a new identity challenge and will encode and attach the challenge // as a header. It returns the identityChallengeValue used for this challenge, so the network can // confirm it later (by passing it to VerifyResponse), or returns an empty challenge if dedupName is @@ -126,7 +154,7 @@ func (i identityChallengePublicKeyScheme) AttachChallenge(attachTo http.Header, return identityChallengeValue{} } c := identityChallenge{ - Key: i.identityKeys.SignatureVerifier, + Key: i.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), PublicAddress: []byte(addr), } @@ -173,7 +201,7 @@ func (i identityChallengePublicKeyScheme) VerifyRequestAndAttachResponse(attachT } // make the response object, encode it and attach it to the header r := identityChallengeResponse{ - Key: i.identityKeys.SignatureVerifier, + Key: i.identityKeys.PublicKey(), Challenge: idChal.Msg.Challenge, ResponseChallenge: newIdentityChallengeValue(), } @@ -271,12 +299,12 @@ type identityVerificationMessageSigned struct { Signature crypto.Signature `codec:"sig"` } -func (i identityChallenge) signAndEncodeB64(s *crypto.SignatureSecrets) string { +func (i identityChallenge) signAndEncodeB64(s identityChallengeSigner) string { signedChal := i.Sign(s) return base64.StdEncoding.EncodeToString(protocol.Encode(&signedChal)) } -func (i identityChallenge) Sign(secrets *crypto.SignatureSecrets) identityChallengeSigned { +func (i identityChallenge) Sign(secrets identityChallengeSigner) identityChallengeSigned { return identityChallengeSigned{Msg: i, Signature: secrets.Sign(i)} } @@ -289,12 +317,12 @@ func (i identityChallengeSigned) Verify() bool { return i.Msg.Key.Verify(i.Msg, i.Signature) } -func (i identityChallengeResponse) signAndEncodeB64(s *crypto.SignatureSecrets) string { +func (i identityChallengeResponse) signAndEncodeB64(s identityChallengeSigner) string { signedChalResp := i.Sign(s) return base64.StdEncoding.EncodeToString(protocol.Encode(&signedChalResp)) } -func (i identityChallengeResponse) Sign(secrets *crypto.SignatureSecrets) identityChallengeResponseSigned { +func (i identityChallengeResponse) Sign(secrets identityChallengeSigner) identityChallengeResponseSigned { return identityChallengeResponseSigned{Msg: i, Signature: secrets.Sign(i)} } @@ -307,7 +335,7 @@ func (i identityChallengeResponseSigned) Verify() bool { return i.Msg.Key.Verify(i.Msg, i.Signature) } -func (i identityVerificationMessage) Sign(secrets *crypto.SignatureSecrets) identityVerificationMessageSigned { +func (i identityVerificationMessage) Sign(secrets identityChallengeSigner) identityVerificationMessageSigned { return identityVerificationMessageSigned{Msg: i, Signature: secrets.Sign(i)} } diff --git a/network/netidentity_test.go b/network/netidentity_test.go index 9650069224..f87480c1b1 100644 --- a/network/netidentity_test.go +++ b/network/netidentity_test.go @@ -180,7 +180,7 @@ func TestIdentityChallengeSchemeBadSignature(t *testing.T) { // Copy the logic of attaching the header and signing so we can sign it wrong c := identityChallengeSigned{ Msg: identityChallenge{ - Key: i.identityKeys.SignatureVerifier, + Key: i.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), PublicAddress: []byte("i1"), }} @@ -232,7 +232,7 @@ func TestIdentityChallengeSchemeBadResponseSignature(t *testing.T) { r := http.Header{} resp := identityChallengeResponseSigned{ Msg: identityChallengeResponse{ - Key: i.identityKeys.SignatureVerifier, + Key: i.identityKeys.PublicKey(), Challenge: origChal, ResponseChallenge: newIdentityChallengeValue(), }} diff --git a/network/netprio.go b/network/netprio.go index 5d91dad8d1..9c6c510608 100644 --- a/network/netprio.go +++ b/network/netprio.go @@ -46,7 +46,7 @@ func prioResponseHandler(message IncomingMessage) OutgoingMessage { addr, err := wn.prioScheme.VerifyPrioResponse(challenge, message.Data) if err != nil { - wn.log.Warnf("prioScheme.VerifyPrioResponse from %s: %v", peer.rootURL, err) + wn.log.Warnf("prioScheme.VerifyPrioResponse from %s: %v", peer.GetAddress(), err) } else { weight := wn.prioScheme.GetPrioWeight(addr) diff --git a/network/p2p/capabilities.go b/network/p2p/capabilities.go new file mode 100644 index 0000000000..e5781aa389 --- /dev/null +++ b/network/p2p/capabilities.go @@ -0,0 +1,179 @@ +// Copyright (C) 2019-2024 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 ( + "context" + "sync" + "time" + + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p/core/discovery" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + libpeerstore "github.com/libp2p/go-libp2p/core/peerstore" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/logging" + algoDht "github.com/algorand/go-algorand/network/p2p/dht" + "github.com/algorand/go-algorand/protocol" +) + +// Capability represents functions that some nodes may provide and other nodes would want to know about +type Capability string + +const ( + // Archival nodes + Archival Capability = "archival" + // Catchpoints storing nodes + Catchpoints = "catchpointStoring" + // Gossip nodes are non permissioned relays + Gossip = "gossip" +) + +const operationTimeout = time.Second * 5 +const maxAdvertisementInterval = time.Hour * 22 + +// CapabilitiesDiscovery exposes Discovery interfaces and wraps underlying DHT methods to provide capabilities advertisement for the node +type CapabilitiesDiscovery struct { + disc discovery.Discovery + dht *dht.IpfsDHT + log logging.Logger + wg sync.WaitGroup +} + +// Advertise implements the discovery.Discovery/discovery.Advertiser interface +func (c *CapabilitiesDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) { + return c.disc.Advertise(ctx, ns, opts...) +} + +// FindPeers implements the discovery.Discovery/discovery.Discoverer interface +func (c *CapabilitiesDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) { + return c.disc.FindPeers(ctx, ns, opts...) +} + +// Close should be called when fully shutting down the node +func (c *CapabilitiesDiscovery) Close() error { + err := c.dht.Close() + c.wg.Wait() + return err +} + +// Host exposes the underlying libp2p host.Host object +func (c *CapabilitiesDiscovery) Host() host.Host { + return c.dht.Host() +} + +// AddPeer adds a given peer.AddrInfo to the Host's Peerstore, and the DHT's routing table +func (c *CapabilitiesDiscovery) AddPeer(p peer.AddrInfo) (bool, error) { + c.Host().Peerstore().AddAddrs(p.ID, p.Addrs, libpeerstore.AddressTTL) + return c.dht.RoutingTable().TryAddPeer(p.ID, true, true) +} + +// PeersForCapability returns a slice of peer.AddrInfo for a Capability +// Since CapabilitiesDiscovery uses a backoffcache, it will attempt to hit cache, then disk, then network +// in order to fetch n peers which are advertising the required capability. +func (c *CapabilitiesDiscovery) PeersForCapability(capability Capability, n int) ([]peer.AddrInfo, error) { + ctx, cancel := context.WithTimeout(context.Background(), operationTimeout) + defer cancel() + var peers []peer.AddrInfo + // +1 because it can include self but we exclude self from the returned list + // that might confuse the caller (and tests assertions) + peersChan, err := c.FindPeers(ctx, string(capability), discovery.Limit(n+1)) + if err != nil { + return nil, err + } + for p := range peersChan { + if p.ID.Size() > 0 && p.ID != c.Host().ID() { + peers = append(peers, p) + } + if len(peers) >= n { + break + } + } + return peers, nil +} + +// AdvertiseCapabilities periodically runs the Advertiser interface on the DHT +// If a capability fails to advertise we will retry every 10 seconds until full success +// This gets rerun every at the minimum ttl or the maxAdvertisementInterval. +func (c *CapabilitiesDiscovery) AdvertiseCapabilities(capabilities ...Capability) { + c.wg.Add(1) + go func() { + // Run the initial Advertisement immediately + nextExecution := time.After(time.Second / 10000) + defer func() { + c.wg.Done() + }() + + for { + select { + case <-c.dht.Context().Done(): + return + case <-nextExecution: + var err error + advertisementInterval := maxAdvertisementInterval + for _, capa := range capabilities { + ttl, err0 := c.Advertise(c.dht.Context(), string(capa)) + if err0 != nil { + err = err0 + c.log.Errorf("failed to advertise for capability %s: %v", capa, err0) + break + } + if ttl < advertisementInterval { + advertisementInterval = ttl + } + c.log.Infof("advertised capability %s", capa) + } + // If we failed to advertise, retry every 10 seconds until successful + if err != nil { + nextExecution = time.After(time.Second * 10) + } else { + // Otherwise, ensure we're at the correct interval + nextExecution = time.After(advertisementInterval) + } + } + } + }() +} + +// Sizer exposes the Size method +type Sizer interface { + Size() int +} + +// RoutingTable exposes some knowledge about the DHT routing table +func (c *CapabilitiesDiscovery) RoutingTable() Sizer { + return c.dht.RoutingTable() +} + +// MakeCapabilitiesDiscovery creates a new CapabilitiesDiscovery object which exposes peer discovery and capabilities advertisement +func MakeCapabilitiesDiscovery(ctx context.Context, cfg config.Local, h host.Host, networkID protocol.NetworkID, log logging.Logger, bootstrapFunc func() []peer.AddrInfo) (*CapabilitiesDiscovery, error) { + discDht, err := algoDht.MakeDHT(ctx, h, networkID, cfg, bootstrapFunc) + if err != nil { + return nil, err + } + discImpl, err := algoDht.MakeDiscovery(discDht) + if err != nil { + return nil, err + } + return &CapabilitiesDiscovery{ + disc: discImpl, + dht: discDht, + log: log, + }, nil +} diff --git a/network/p2p/capabilities_test.go b/network/p2p/capabilities_test.go new file mode 100644 index 0000000000..881860f647 --- /dev/null +++ b/network/p2p/capabilities_test.go @@ -0,0 +1,354 @@ +// Copyright (C) 2019-2024 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 ( + "context" + "math/rand" + "sync" + "testing" + "time" + + golog "github.com/ipfs/go-log" + "github.com/libp2p/go-libp2p" + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p/core/discovery" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/logging" + algodht "github.com/algorand/go-algorand/network/p2p/dht" + "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestCapabilities_Discovery(t *testing.T) { + partitiontest.PartitionTest(t) + + golog.SetDebugLogging() + var caps []*CapabilitiesDiscovery + var addrs []peer.AddrInfo + testSize := 3 + for i := 0; i < testSize; i++ { + tempdir := t.TempDir() + ps, err := peerstore.NewPeerStore(nil, "") + require.NoError(t, err) + h, _, err := MakeHost(config.GetDefaultLocal(), tempdir, ps) + require.NoError(t, err) + capD, err := MakeCapabilitiesDiscovery(context.Background(), config.GetDefaultLocal(), h, "devtestnet", logging.Base(), func() []peer.AddrInfo { return nil }) + require.NoError(t, err) + caps = append(caps, capD) + addrs = append(addrs, peer.AddrInfo{ + ID: capD.Host().ID(), + Addrs: capD.Host().Addrs(), + }) + } + for _, capD := range caps { + peersAdded := 0 + for _, addr := range addrs { + added, err := capD.AddPeer(addr) + require.NoError(t, err) + require.True(t, added) + peersAdded++ + } + err := capD.dht.Bootstrap(context.Background()) + require.NoError(t, err) + capD.dht.ForceRefresh() + require.Equal(t, peersAdded, capD.dht.RoutingTable().Size()) + } +} + +func setupDHTHosts(t *testing.T, numHosts int) []*dht.IpfsDHT { + var hosts []host.Host + var bootstrapPeers []peer.AddrInfo + var dhts []*dht.IpfsDHT + cfg := config.GetDefaultLocal() + for i := 0; i < numHosts; i++ { + tmpdir := t.TempDir() + pk, err := GetPrivKey(cfg, tmpdir) + require.NoError(t, err) + ps, err := peerstore.NewPeerStore([]*peer.AddrInfo{}, "") + require.NoError(t, err) + h, err := libp2p.New( + libp2p.ListenAddrStrings("/dns4/localhost/tcp/0"), + libp2p.Identity(pk), + libp2p.Peerstore(ps)) + require.NoError(t, err) + hosts = append(hosts, h) + bootstrapPeers = append(bootstrapPeers, peer.AddrInfo{ID: h.ID(), Addrs: h.Addrs()}) + } + for _, h := range hosts { + ht, err := algodht.MakeDHT(context.Background(), h, "devtestnet", cfg, func() []peer.AddrInfo { return bootstrapPeers }) + require.NoError(t, err) + // this is a workaround for the following issue + // "failed to negotiate security protocol: error reading handshake message: noise: message is too short" + // it appears simultaneous connection attempts (dht.New() attempts to connect) causes this handshake error. + // https://github.com/libp2p/go-libp2p-noise/issues/70 + time.Sleep(200 * time.Millisecond) + + err = ht.Bootstrap(context.Background()) + require.NoError(t, err) + dhts = append(dhts, ht) + } + return dhts +} + +func waitForRouting(t *testing.T, disc *CapabilitiesDiscovery) { + refreshCtx, refCancel := context.WithTimeout(context.Background(), time.Second*5) + for { + select { + case <-refreshCtx.Done(): + refCancel() + require.Fail(t, "failed to populate routing table before timeout") + default: + if disc.dht.RoutingTable().Size() > 0 { + refCancel() + return + } + } + } +} + +func setupCapDiscovery(t *testing.T, numHosts int, numBootstrapPeers int) []*CapabilitiesDiscovery { + var hosts []host.Host + var bootstrapPeers []peer.AddrInfo + var capsDisc []*CapabilitiesDiscovery + cfg := config.GetDefaultLocal() + for i := 0; i < numHosts; i++ { + tmpdir := t.TempDir() + pk, err := GetPrivKey(cfg, tmpdir) + require.NoError(t, err) + ps, err := peerstore.NewPeerStore([]*peer.AddrInfo{}, "") + require.NoError(t, err) + h, err := libp2p.New( + libp2p.ListenAddrStrings("/dns4/localhost/tcp/0"), + libp2p.Identity(pk), + libp2p.Peerstore(ps)) + require.NoError(t, err) + hosts = append(hosts, h) + bootstrapPeers = append(bootstrapPeers, peer.AddrInfo{ID: h.ID(), Addrs: h.Addrs()}) + } + for _, h := range hosts { + bp := bootstrapPeers + if numBootstrapPeers != 0 && numBootstrapPeers != numHosts { + bp = make([]peer.AddrInfo, len(bootstrapPeers)) + copy(bp, bootstrapPeers) + rand.Shuffle(len(bootstrapPeers), func(i, j int) { + bp[i], bp[j] = bp[j], bp[i] + }) + bp = bp[:numBootstrapPeers] + } + ht, err := algodht.MakeDHT(context.Background(), h, "devtestnet", cfg, func() []peer.AddrInfo { return bp }) + require.NoError(t, err) + // this is a workaround for the following issue + // "failed to negotiate security protocol: error reading handshake message: noise: message is too short" + // it appears simultaneous connection attempts (dht.New() attempts to connect) causes this handshake error. + // https://github.com/libp2p/go-libp2p-noise/issues/70 + time.Sleep(200 * time.Millisecond) + + disc, err := algodht.MakeDiscovery(ht) + require.NoError(t, err) + cd := &CapabilitiesDiscovery{ + disc: disc, + dht: ht, + log: logging.Base(), + } + capsDisc = append(capsDisc, cd) + } + return capsDisc +} + +func TestCapabilities_DHTTwoPeers(t *testing.T) { + partitiontest.PartitionTest(t) + + numAdvertisers := 2 + dhts := setupDHTHosts(t, numAdvertisers) + topic := "foobar" + for i, ht := range dhts { + disc, err := algodht.MakeDiscovery(ht) + require.NoError(t, err) + refreshCtx, refCancel := context.WithTimeout(context.Background(), time.Second*5) + peersPopulated: + for { + select { + case <-refreshCtx.Done(): + refCancel() + require.Fail(t, "failed to populate routing table before timeout") + default: + if ht.RoutingTable().Size() > 0 { + refCancel() + break peersPopulated + } + } + } + _, err = disc.Advertise(context.Background(), topic) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + var advertisers []peer.AddrInfo + peersChan, err := disc.FindPeers(ctx, topic, discovery.Limit(numAdvertisers)) + require.NoError(t, err) + pollingForPeers: + for { + select { + case p, open := <-peersChan: + if p.ID.Size() > 0 { + advertisers = append(advertisers, p) + } + if !open { + break pollingForPeers + } + } + } + cancel() + // Returned peers will include the querying node's ID since it advertises for the topic as well + require.Equal(t, i+1, len(advertisers)) + } +} + +func TestCapabilities_Varying(t *testing.T) { + partitiontest.PartitionTest(t) + + const numAdvertisers = 10 + + var tests = []struct { + name string + numBootstrap int + }{ + {"bootstrap=all", numAdvertisers}, + {"bootstrap=2", 2}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + capsDisc := setupCapDiscovery(t, numAdvertisers, test.numBootstrap) + noCap := capsDisc[:3] + archOnly := capsDisc[3:5] + catchOnly := capsDisc[5:7] + archCatch := capsDisc[7:] + + var wg sync.WaitGroup + wg.Add(len(archOnly) + len(catchOnly) + len(archCatch)) + for _, disc := range archOnly { + go func(disc *CapabilitiesDiscovery) { + defer wg.Done() + waitForRouting(t, disc) + disc.AdvertiseCapabilities(Archival) + }(disc) + } + for _, disc := range catchOnly { + go func(disc *CapabilitiesDiscovery) { + defer wg.Done() + waitForRouting(t, disc) + disc.AdvertiseCapabilities(Catchpoints) + }(disc) + } + for _, disc := range archCatch { + go func(disc *CapabilitiesDiscovery) { + defer wg.Done() + waitForRouting(t, disc) + disc.AdvertiseCapabilities(Archival, Catchpoints) + }(disc) + } + + wg.Wait() + + wg.Add(len(noCap) * 2) + for _, disc := range noCap { + go func(disc *CapabilitiesDiscovery) { + defer wg.Done() + require.Eventuallyf(t, + func() bool { + numArchPeers := len(archOnly) + len(archCatch) + peers, err := disc.PeersForCapability(Archival, numArchPeers) + if err == nil && len(peers) == numArchPeers { + return true + } + return false + }, + time.Minute, + time.Second, + "Not all expected archival peers were found", + ) + }(disc) + + go func(disc *CapabilitiesDiscovery) { + defer wg.Done() + require.Eventuallyf(t, + func() bool { + numCatchPeers := len(catchOnly) + len(archCatch) + peers, err := disc.PeersForCapability(Catchpoints, numCatchPeers) + if err == nil && len(peers) == numCatchPeers { + return true + } + return false + }, + time.Minute, + time.Second, + "Not all expected catchpoint peers were found", + ) + }(disc) + } + + wg.Wait() + + for _, disc := range capsDisc[3:] { + err := disc.Close() + require.NoError(t, err) + // Make sure it actually closes + disc.wg.Wait() + } + }) + } +} + +func TestCapabilities_ExcludesSelf(t *testing.T) { + partitiontest.PartitionTest(t) + disc := setupCapDiscovery(t, 2, 2) + + testPeersFound := func(disc *CapabilitiesDiscovery, n int, cap Capability) bool { + peers, err := disc.PeersForCapability(cap, n) + if err == nil && len(peers) == n { + return true + } + return false + } + + waitForRouting(t, disc[0]) + disc[0].AdvertiseCapabilities(Archival) + // disc[1] finds Archival + require.Eventuallyf(t, + func() bool { return testPeersFound(disc[1], 1, Archival) }, + time.Minute, + time.Second, + "Could not find archival peer", + ) + + // disc[0] doesn't find itself + require.Neverf(t, + func() bool { return testPeersFound(disc[0], 1, Archival) }, + time.Second*5, + time.Second, + "Found self when searching for capability", + ) + + err := disc[0].Close() + require.NoError(t, err) + disc[0].wg.Wait() +} diff --git a/network/p2p/dht/dht.go b/network/p2p/dht/dht.go new file mode 100644 index 0000000000..1ea38bdd78 --- /dev/null +++ b/network/p2p/dht/dht.go @@ -0,0 +1,75 @@ +// Copyright (C) 2019-2024 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 dht + +import ( + "context" + "fmt" + "math/rand" + "time" + + dht "github.com/libp2p/go-libp2p-kad-dht" + dhtmetrics "github.com/libp2p/go-libp2p-kad-dht/metrics" + "github.com/libp2p/go-libp2p/core/discovery" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + crouting "github.com/libp2p/go-libp2p/core/routing" + "github.com/libp2p/go-libp2p/p2p/discovery/backoff" + "github.com/libp2p/go-libp2p/p2p/discovery/routing" + "go.opencensus.io/stats/view" + + "github.com/algorand/go-algorand/config" + algoproto "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util/metrics" +) + +const minBackoff = time.Second * 5 +const maxBackoff = time.Second * 20 +const baseBackoff = float64(1.1) + +func dhtProtocolPrefix(networkID algoproto.NetworkID) protocol.ID { + return protocol.ID(fmt.Sprintf("/algorand/kad/%s", networkID)) +} + +// MakeDHT creates the dht.IpfsDHT object +func MakeDHT(ctx context.Context, h host.Host, networkID algoproto.NetworkID, cfg config.Local, bootstrapFunc func() []peer.AddrInfo) (*dht.IpfsDHT, error) { + dhtCfg := []dht.Option{ + // Automatically determine server or client mode + dht.Mode(dht.ModeAutoServer), + // We don't need the value store right now + dht.DisableValues(), + dht.ProtocolPrefix(dhtProtocolPrefix(networkID)), + dht.BootstrapPeersFunc(bootstrapFunc), + } + + if err := view.Register(dhtmetrics.DefaultViews...); err != nil { + return nil, err + } + metrics.DefaultRegistry().Register(&metrics.OpencensusDefaultMetrics) + + return dht.New(ctx, h, dhtCfg...) +} + +func backoffFactory() backoff.BackoffFactory { + return backoff.NewExponentialDecorrelatedJitter(minBackoff, maxBackoff, baseBackoff, rand.NewSource(rand.Int63())) +} + +// MakeDiscovery creates a discovery.Discovery object using backoff and cacching +func MakeDiscovery(r crouting.ContentRouting) (discovery.Discovery, error) { + return backoff.NewBackoffDiscovery(routing.NewRoutingDiscovery(r), backoffFactory(), backoff.WithBackoffDiscoveryReturnedChannelSize(0), backoff.WithBackoffDiscoverySimultaneousQueryBufferSize(0)) +} diff --git a/network/p2p/dht/dht_test.go b/network/p2p/dht/dht_test.go new file mode 100644 index 0000000000..51cb8978f7 --- /dev/null +++ b/network/p2p/dht/dht_test.go @@ -0,0 +1,64 @@ +// Copyright (C) 2019-2024 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 dht + +import ( + "context" + "testing" + + logging "github.com/ipfs/go-log" + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestDHTBasic(t *testing.T) { + partitiontest.PartitionTest(t) + + h, err := libp2p.New() + require.NoError(t, err) + dht, err := MakeDHT( + context.Background(), + h, + "devtestnet", + config.GetDefaultLocal(), + func() []peer.AddrInfo { return nil }) + require.NoError(t, err) + _, err = MakeDiscovery(dht) + require.NoError(t, err) + err = dht.Bootstrap(context.Background()) + require.NoError(t, err) +} + +func TestDHTBasicAlgodev(t *testing.T) { + partitiontest.PartitionTest(t) + + logging.SetDebugLogging() + h, err := libp2p.New() + require.NoError(t, err) + cfg := config.GetDefaultLocal() + cfg.DNSBootstrapID = ".algodev.network" + dht, err := MakeDHT(context.Background(), h, "betanet", cfg, func() []peer.AddrInfo { return nil }) + require.NoError(t, err) + _, err = MakeDiscovery(dht) + require.NoError(t, err) + err = dht.Bootstrap(context.Background()) + require.NoError(t, err) +} diff --git a/network/p2p/dnsaddr/resolve.go b/network/p2p/dnsaddr/resolve.go index 176d62f946..5e0e8007fc 100644 --- a/network/p2p/dnsaddr/resolve.go +++ b/network/p2p/dnsaddr/resolve.go @@ -30,13 +30,19 @@ func isDnsaddr(maddr multiaddr.Multiaddr) bool { } // Iterate runs through the resolvable dnsaddrs in the tree using the resolveController and invokes f for each dnsaddr node lookup -func Iterate(initial multiaddr.Multiaddr, controller *MultiaddrDNSResolveController, f func(dnsaddr multiaddr.Multiaddr, entries []multiaddr.Multiaddr) error) error { +func Iterate(initial multiaddr.Multiaddr, controller ResolveController, f func(dnsaddr multiaddr.Multiaddr, entries []multiaddr.Multiaddr) error) error { resolver := controller.Resolver() if resolver == nil { return errors.New("passed controller has no resolvers Iterate") } + const maxHops = 25 // any reasonable number to prevent infinite loop in case of circular dnsaddr + hops := 0 var toResolve = []multiaddr.Multiaddr{initial} for resolver != nil && len(toResolve) > 0 { + hops++ + if hops > maxHops { + return errors.New("max hops reached while resolving dnsaddr " + initial.String()) + } curr := toResolve[0] maddrs, resolveErr := resolver.Resolve(context.Background(), curr) if resolveErr != nil { @@ -64,7 +70,7 @@ func Iterate(initial multiaddr.Multiaddr, controller *MultiaddrDNSResolveControl // Any further dnsaddrs will be looked up until all TXT records have been fetched, // and the full list of resulting Multiaddrs is returned. // It uses the MultiaddrDNSResolveController to cycle through DNS resolvers on failure. -func MultiaddrsFromResolver(domain string, controller *MultiaddrDNSResolveController) ([]multiaddr.Multiaddr, error) { +func MultiaddrsFromResolver(domain string, controller ResolveController) ([]multiaddr.Multiaddr, error) { dnsaddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/dnsaddr/%s", domain)) if err != nil { return nil, fmt.Errorf("unable to construct multiaddr for %s : %v", domain, err) diff --git a/network/p2p/dnsaddr/resolveController.go b/network/p2p/dnsaddr/resolveController.go index 73a46243d0..ff606b39a1 100644 --- a/network/p2p/dnsaddr/resolveController.go +++ b/network/p2p/dnsaddr/resolveController.go @@ -17,15 +17,29 @@ package dnsaddr import ( + "context" + + "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" log "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/tools/network" ) +// Resolver is an interface for resolving dnsaddrs +type Resolver interface { + Resolve(ctx context.Context, maddr multiaddr.Multiaddr) ([]multiaddr.Multiaddr, error) +} + +// ResolveController is an interface for cycling through resolvers +type ResolveController interface { + Resolver() Resolver + NextResolver() Resolver +} + // MultiaddrDNSResolveController returns a madns.Resolver, cycling through underlying net.Resolvers type MultiaddrDNSResolveController struct { - resolver *madns.Resolver + resolver Resolver nextResolvers []func() *madns.Resolver controller network.ResolveController } @@ -45,7 +59,7 @@ func NewMultiaddrDNSResolveController(secure bool, fallbackDNSResolverAddress st } // NextResolver applies the nextResolvers functions in order and returns the most recent result -func (c *MultiaddrDNSResolveController) NextResolver() *madns.Resolver { +func (c *MultiaddrDNSResolveController) NextResolver() Resolver { if len(c.nextResolvers) == 0 { c.resolver = nil } else { @@ -56,7 +70,7 @@ func (c *MultiaddrDNSResolveController) NextResolver() *madns.Resolver { } // Resolver returns the current resolver, invokes NextResolver if the resolver is nil -func (c *MultiaddrDNSResolveController) Resolver() *madns.Resolver { +func (c *MultiaddrDNSResolveController) Resolver() Resolver { if c.resolver == nil { c.resolver = c.NextResolver() } diff --git a/network/p2p/dnsaddr/resolve_test.go b/network/p2p/dnsaddr/resolve_test.go index 937e4db183..30acbd3e5f 100644 --- a/network/p2p/dnsaddr/resolve_test.go +++ b/network/p2p/dnsaddr/resolve_test.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "testing" + "time" "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" @@ -36,22 +37,22 @@ func TestIsDnsaddr(t *testing.T) { t.Parallel() testcases := []struct { - name string - addr string - expected bool + name string + addr string + isDnsaddr bool }{ - {name: "DnsAddr", addr: "/dnsaddr/foobar.com", expected: true}, - {name: "DnsAddrWithPeerId", addr: "/dnsaddr/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: true}, - {name: "DnsAddrWithIPPeerId", addr: "/dnsaddr/foobar.com/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: true}, - {name: "Dns4Addr", addr: "/dns4/foobar.com/", expected: false}, - {name: "Dns6Addr", addr: "/dns6/foobar.com/", expected: false}, - {name: "Dns4AddrWithPeerId", addr: "/dns4/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", expected: false}, + {name: "DnsAddr", addr: "/dnsaddr/foobar.com", isDnsaddr: true}, + {name: "DnsAddrWithPeerId", addr: "/dnsaddr/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", isDnsaddr: true}, + {name: "DnsAddrWithIPPeerId", addr: "/dnsaddr/foobar.com/ip4/127.0.0.1/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", isDnsaddr: true}, + {name: "Dns4Addr", addr: "/dns4/foobar.com/", isDnsaddr: false}, + {name: "Dns6Addr", addr: "/dns6/foobar.com/", isDnsaddr: false}, + {name: "Dns4AddrWithPeerId", addr: "/dns4/foobar.com/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", isDnsaddr: false}, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { maddr, err := multiaddr.NewMultiaddr(testcase.addr) require.NoError(t, err) - require.Equal(t, testcase.expected, isDnsaddr(maddr)) + require.Equal(t, testcase.isDnsaddr, isDnsaddr(maddr)) }) } } @@ -109,3 +110,42 @@ func TestMultiaddrsFromResolverDnsFailure(t *testing.T) { assert.Empty(t, maddrs) assert.ErrorContains(t, err, "always errors") } + +type mockController struct { +} + +func (c mockController) Resolver() Resolver { + return selfResolver{} +} + +func (c mockController) NextResolver() Resolver { + return nil +} + +type selfResolver struct { +} + +func (r selfResolver) Resolve(ctx context.Context, maddr multiaddr.Multiaddr) ([]multiaddr.Multiaddr, error) { + return []multiaddr.Multiaddr{maddr}, nil +} + +// TestIterate ensures the Iterate() does not hang in infinite loop +// when resolver returns the same dnsaddr +func TestIterate(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + dnsAddr := "/dnsaddr/foobar.com" + require.True(t, isDnsaddr(multiaddr.StringCast(dnsAddr))) + ma, err := multiaddr.NewMultiaddr(dnsAddr) + require.NoError(t, err) + + require.Eventually(t, func() bool { + Iterate( + ma, + mockController{}, + func(dnsaddr multiaddr.Multiaddr, entries []multiaddr.Multiaddr) error { return nil }, + ) + return true + }, 100*time.Millisecond, 50*time.Millisecond) +} diff --git a/network/p2p/http.go b/network/p2p/http.go new file mode 100644 index 0000000000..9f2622d015 --- /dev/null +++ b/network/p2p/http.go @@ -0,0 +1,92 @@ +// Copyright (C) 2019-2024 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 ( + "net/http" + "sync" + "time" + + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/limitcaller" + "github.com/gorilla/mux" + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + libp2phttp "github.com/libp2p/go-libp2p/p2p/http" +) + +// algorandP2pHTTPProtocol defines a libp2p protocol name for algorand's http over p2p messages +const algorandP2pHTTPProtocol = "/algorand-http/1.0.0" + +// HTTPServer is a wrapper around libp2phttp.Host that allows registering http handlers with path parameters. +type HTTPServer struct { + libp2phttp.Host + p2phttpMux *mux.Router + p2phttpMuxRegistrarOnce sync.Once +} + +// MakeHTTPServer creates a new HTTPServer +func MakeHTTPServer(streamHost host.Host) *HTTPServer { + httpServer := HTTPServer{ + Host: libp2phttp.Host{StreamHost: streamHost}, + p2phttpMux: mux.NewRouter(), + } + return &httpServer +} + +// RegisterHTTPHandler registers a http handler with a given path. +func (s *HTTPServer) RegisterHTTPHandler(path string, handler http.Handler) { + s.p2phttpMux.Handle(path, handler) + s.p2phttpMuxRegistrarOnce.Do(func() { + s.Host.SetHTTPHandlerAtPath(algorandP2pHTTPProtocol, "/", s.p2phttpMux) + }) +} + +// MakeHTTPClient creates a http.Client that uses libp2p transport for a given protocol and peer address. +func MakeHTTPClient(addrInfo *peer.AddrInfo) (*http.Client, error) { + clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs) + if err != nil { + return nil, err + } + logging.Base().Debugf("MakeHTTPClient made a new P2P host %s for %s", clientStreamHost.ID(), addrInfo.String()) + + client := libp2phttp.Host{StreamHost: clientStreamHost} + + // Do not use client.NamespacedClient to prevent it making connection to a well-known handler + // to make a NamespaceRoundTripper that limits to specific URL paths. + // First, we do not want make requests when listing peers (the main MakeHTTPClient invoker). + // Secondly, this makes unit testing easier - no need to register fake handlers. + rt, err := client.NewConstrainedRoundTripper(*addrInfo) + if err != nil { + return nil, err + } + + return &http.Client{Transport: rt}, nil +} + +// MakeHTTPClientWithRateLimit creates a http.Client that uses libp2p transport for a given protocol and peer address. +func MakeHTTPClientWithRateLimit(addrInfo *peer.AddrInfo, pstore limitcaller.ConnectionTimeStore, queueingTimeout time.Duration, maxIdleConnsPerHost int) (*http.Client, error) { + cl, err := MakeHTTPClient(addrInfo) + if err != nil { + return nil, err + } + rlrt := limitcaller.MakeRateLimitingTransportWithRoundTripper(pstore, queueingTimeout, cl.Transport, addrInfo, maxIdleConnsPerHost) + cl.Transport = &rlrt + return cl, nil + +} diff --git a/network/p2p/logger.go b/network/p2p/logger.go new file mode 100644 index 0000000000..26c738e1e1 --- /dev/null +++ b/network/p2p/logger.go @@ -0,0 +1,123 @@ +// Copyright (C) 2019-2024 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 . + +// This package implement a zap.Core in order to wrap lip2p logger into algod's logger. + +package p2p + +import ( + "runtime" + "strings" + + p2plogging "github.com/ipfs/go-log/v2" + "github.com/sirupsen/logrus" + "go.uber.org/zap/zapcore" + + "github.com/algorand/go-algorand/logging" +) + +// var levelsMap = map[logging.Level]zapcore.Level{ +// logging.Debug: zapcore.DebugLevel, +// logging.Info: zapcore.InfoLevel, +// logging.Warn: zapcore.WarnLevel, +// logging.Error: zapcore.ErrorLevel, +// logging.Fatal: zapcore.FatalLevel, +// logging.Panic: zapcore.PanicLevel, +// } + +var levelsMap = map[zapcore.Level]logging.Level{ + zapcore.DebugLevel: logging.Debug, + zapcore.InfoLevel: logging.Info, + zapcore.WarnLevel: logging.Warn, + zapcore.ErrorLevel: logging.Error, + zapcore.FatalLevel: logging.Fatal, + zapcore.PanicLevel: logging.Panic, +} + +// loggingCore implements zapcore.Core +type loggingCore struct { + log logging.Logger + level logging.Level + fields []zapcore.Field + zapcore.Core +} + +// EnableP2PLogging enables libp2p logging into the provided logger with the provided level. +func EnableP2PLogging(log logging.Logger, l logging.Level) { + core := loggingCore{ + log: log, + level: l, + } + for p2pLevel, logLevel := range levelsMap { + if logLevel == l { + p2plogging.SetAllLoggers(p2plogging.LogLevel(p2pLevel)) + break + } + } + p2plogging.SetPrimaryCore(&core) +} + +func (c *loggingCore) Enabled(l zapcore.Level) bool { + level := levelsMap[l] + return c.log.IsLevelEnabled(level) +} + +func (c *loggingCore) With(fields []zapcore.Field) zapcore.Core { + return &loggingCore{ + log: c.log, + level: c.level, + fields: append(c.fields, fields...), + } +} + +func (c *loggingCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if c.Enabled(e.Level) { + return ce.AddCore(e, c) + } + return ce +} + +func (c *loggingCore) Write(e zapcore.Entry, fields []zapcore.Field) error { + allFields := append(c.fields, fields...) + loggingFields := make(logging.Fields, len(allFields)) + + for _, f := range allFields { + if len(f.String) > 0 { + loggingFields[f.Key] = f.String + } else if f.Interface != nil { + loggingFields[f.Key] = f.Interface + } else { + loggingFields[f.Key] = f.Integer + } + } + event := c.log.WithFields(loggingFields).With("libp2p", e.LoggerName) + file := e.Caller.File + slash := strings.LastIndex(file, "/") + file = file[slash+1:] + event = event.WithFields(logrus.Fields{ + "file": file, + "line": e.Caller.Line, + }) + if function := runtime.FuncForPC(e.Caller.PC); function != nil { + event = event.With("function", function.Name()) + } + event.Entry().Log(logrus.Level(levelsMap[e.Level]), e.Message) + return nil +} + +func (c *loggingCore) Sync() error { + return nil +} diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index cd84f1c69c..f4ed670f3e 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -18,6 +18,7 @@ package p2p import ( "context" + "encoding/base32" "fmt" "runtime" "strings" @@ -25,22 +26,37 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" + pstore "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/network/phonebook" + "github.com/algorand/go-algorand/util/metrics" "github.com/algorand/go-deadlock" "github.com/libp2p/go-libp2p" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/libp2p/go-libp2p/core/protocol" + libp2phttp "github.com/libp2p/go-libp2p/p2p/http" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" + "github.com/libp2p/go-libp2p/p2p/security/noise" "github.com/libp2p/go-libp2p/p2p/transport/tcp" + "github.com/multiformats/go-multiaddr" ) +// SubNextCancellable is an abstraction for pubsub.Subscription +type SubNextCancellable interface { + Next(ctx context.Context) (*pubsub.Message, error) + Cancel() +} + // Service defines the interface used by the network integrating with underlying p2p implementation type Service interface { + Start() error Close() error - ID() peer.ID // return peer.ID for self + ID() peer.ID // return peer.ID for self + IDSigner() *PeerIDChallengeSigner AddrInfo() peer.AddrInfo // return addrInfo for self DialNode(context.Context, *peer.AddrInfo) error @@ -49,17 +65,21 @@ type Service interface { Conns() []network.Conn ListPeersForTopic(topic string) []peer.ID - Subscribe(topic string, val pubsub.ValidatorEx) (*pubsub.Subscription, error) + Subscribe(topic string, val pubsub.ValidatorEx) (SubNextCancellable, error) Publish(ctx context.Context, topic string, data []byte) error + + GetStream(peer.ID) (network.Stream, bool) } // serviceImpl manages integration with libp2p and implements the Service interface type serviceImpl struct { - log logging.Logger - host host.Host - streams *streamManager - pubsub *pubsub.PubSub - pubsubCtx context.Context + log logging.Logger + listenAddr string + host host.Host + streams *streamManager + pubsub *pubsub.PubSub + pubsubCtx context.Context + privKey crypto.PrivKey topics map[string]*pubsub.Topic topicsMu deadlock.RWMutex @@ -68,14 +88,19 @@ type serviceImpl struct { // AlgorandWsProtocol defines a libp2p protocol name for algorand's websockets messages const AlgorandWsProtocol = "/algorand-ws/1.0.0" +// algorandGUIDProtocolPrefix defines a libp2p protocol name for algorand node telemetry GUID exchange +const algorandGUIDProtocolPrefix = "/algorand-telemetry/1.0.0/" +const algorandGUIDProtocolTemplate = algorandGUIDProtocolPrefix + "%s/%s" + const dialTimeout = 30 * time.Second -// MakeService creates a P2P service instance -func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, datadir string, pstore peerstore.Peerstore, wsStreamHandler StreamHandler) (*serviceImpl, error) { +// MakeHost creates a libp2p host but does not start listening. +// Use host.Network().Listen() on the returned address to start listening. +func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host.Host, string, error) { // load stored peer ID, or make ephemeral peer ID privKey, err := GetPrivKey(cfg, datadir) if err != nil { - return nil, err + return nil, "", err } // muxer supports tweaking fields from yamux.Config @@ -93,38 +118,110 @@ func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, data listenAddr = "/ip4/0.0.0.0/tcp/0" } - h, err := libp2p.New( + // the libp2p.NoListenAddrs builtin disables relays but this one does not + var noListenAddrs = func(cfg *libp2p.Config) error { + cfg.ListenAddrs = []multiaddr.Multiaddr{} + return nil + } + + var disableMetrics = func(cfg *libp2p.Config) error { return nil } + metrics.DefaultRegistry().Register(&metrics.PrometheusDefaultMetrics) + + host, err := libp2p.New( libp2p.Identity(privKey), libp2p.UserAgent(ua), libp2p.Transport(tcp.NewTCPTransport), libp2p.Muxer("/yamux/1.0.0", &ymx), libp2p.Peerstore(pstore), - libp2p.ListenAddrStrings(listenAddr), + noListenAddrs, + libp2p.Security(noise.ID, noise.New), + disableMetrics, ) - if err != nil { - return nil, err + return &StreamChainingHost{ + Host: host, + handlers: map[protocol.ID][]network.StreamHandler{}, + }, listenAddr, err +} + +// StreamChainingHost is a wrapper around host.Host that overrides SetStreamHandler +// to allow chaining multiple handlers for the same protocol. +// Note, there should be probably only single handler that writes/reads streams. +type StreamChainingHost struct { + host.Host + handlers map[protocol.ID][]network.StreamHandler + mutex deadlock.Mutex +} + +// SetStreamHandler overrides the host.Host.SetStreamHandler method for chaining multiple handlers. +// Function objects are not comparable so theoretically it could have duplicates. +// The main use case is to track HTTP streams for ProtocolIDForMultistreamSelect = "/http/1.1" +// so it could just filter for such protocol if there any issues with other protocols like kad or mesh. +func (h *StreamChainingHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) { + h.mutex.Lock() + defer h.mutex.Unlock() + + handlers := h.handlers[pid] + if len(handlers) == 0 { + // no other handlers, do not set a proxy handler + h.Host.SetStreamHandler(pid, handler) + h.handlers[pid] = append(handlers, handler) + return } - log.Infof("P2P service started: peer ID %s addrs %s", h.ID(), h.Addrs()) + // otherwise chain the handlers with a copy of the existing handlers + handlers = append(handlers, handler) + // copy to save it in the closure and call lock free + currentHandlers := make([]network.StreamHandler, len(handlers)) + copy(currentHandlers, handlers) + h.Host.SetStreamHandler(pid, func(s network.Stream) { + for _, h := range currentHandlers { + h(s) + } + }) + h.handlers[pid] = handlers +} + +// MakeService creates a P2P service instance +func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler, bootstrapPeers []*peer.AddrInfo) (*serviceImpl, error) { sm := makeStreamManager(ctx, log, h, wsStreamHandler) h.Network().Notify(sm) h.SetStreamHandler(AlgorandWsProtocol, sm.streamHandler) + h.SetStreamHandler(libp2phttp.ProtocolIDForMultistreamSelect, sm.streamHandlerHTTP) + + // set an empty handler for telemetryID/telemetryInstance protocol in order to allow other peers to know our telemetryID + telemetryID := log.GetTelemetryGUID() + telemetryInstance := log.GetInstanceName() + telemetryProtoInfo := formatPeerTelemetryInfoProtocolName(telemetryID, telemetryInstance) + h.SetStreamHandler(protocol.ID(telemetryProtoInfo), func(s network.Stream) { s.Close() }) ps, err := makePubSub(ctx, cfg, h) if err != nil { return nil, err } - return &serviceImpl{ - log: log, - host: h, - streams: sm, - pubsub: ps, - pubsubCtx: ctx, - topics: make(map[string]*pubsub.Topic), + + log: log, + listenAddr: listenAddr, + host: h, + streams: sm, + pubsub: ps, + pubsubCtx: ctx, + privKey: h.Peerstore().PrivKey(h.ID()), + topics: make(map[string]*pubsub.Topic), }, nil } +// Start starts the P2P service +func (s *serviceImpl) Start() error { + listenAddr, err := multiaddr.NewMultiaddr(s.listenAddr) + if err != nil { + s.log.Errorf("failed to create multiaddress: %s", err) + return err + } + + return s.host.Network().Listen(listenAddr) +} + // Close shuts down the P2P service func (s *serviceImpl) Close() error { return s.host.Close() @@ -135,22 +232,35 @@ func (s *serviceImpl) ID() peer.ID { return s.host.ID() } +// IDSigner returns a PeerIDChallengeSigner that implements the network identityChallengeSigner interface +func (s *serviceImpl) IDSigner() *PeerIDChallengeSigner { + return &PeerIDChallengeSigner{key: s.privKey} +} + // DialPeersUntilTargetCount attempts to establish connections to the provided phonebook addresses func (s *serviceImpl) DialPeersUntilTargetCount(targetConnCount int) { - peerIDs := s.host.Peerstore().Peers() - for _, peerID := range peerIDs { + ps := s.host.Peerstore().(*pstore.PeerStore) + peerIDs := ps.GetAddresses(targetConnCount, phonebook.PhoneBookEntryRelayRole) + conns := s.host.Network().Conns() + var numOutgoingConns int + for _, conn := range conns { + if conn.Stat().Direction == network.DirOutbound { + numOutgoingConns++ + } + } + for _, peerInfo := range peerIDs { + peerInfo := peerInfo.(*peer.AddrInfo) // if we are at our target count stop trying to connect - if len(s.host.Network().Conns()) == targetConnCount { + if numOutgoingConns >= targetConnCount { return } // if we are already connected to this peer, skip it - if len(s.host.Network().ConnsToPeer(peerID)) > 0 { + if len(s.host.Network().ConnsToPeer(peerInfo.ID)) > 0 { continue } - peerInfo := s.host.Peerstore().PeerInfo(peerID) - err := s.DialNode(context.Background(), &peerInfo) // leaving the calls as blocking for now, to not over-connect beyond fanout + err := s.DialNode(context.Background(), peerInfo) // leaving the calls as blocking for now, to not over-connect beyond fanout if err != nil { - s.log.Warnf("failed to connect to peer %s: %v", peerID, err) + s.log.Warnf("failed to connect to peer %s: %v", peerInfo.ID, err) } } } @@ -184,6 +294,10 @@ func (s *serviceImpl) ClosePeer(peer peer.ID) error { return s.host.Network().ClosePeer(peer) } +func (s *serviceImpl) GetStream(peerID peer.ID) (network.Stream, bool) { + return s.streams.getStream(peerID) +} + // netAddressToListenAddress converts a netAddress in "ip:port" format to a listen address // that can be passed in to libp2p.ListenAddrStrings func netAddressToListenAddress(netAddress string) (string, error) { @@ -203,3 +317,32 @@ func netAddressToListenAddress(netAddress string) (string, error) { return fmt.Sprintf("/ip4/%s/tcp/%s", ip, parts[1]), nil } + +// GetPeerTelemetryInfo returns the telemetry ID of a peer by looking at its protocols +func GetPeerTelemetryInfo(peerProtocols []protocol.ID) (telemetryID string, telemetryInstance string) { + for _, protocol := range peerProtocols { + if strings.HasPrefix(string(protocol), algorandGUIDProtocolPrefix) { + telemetryInfo := string(protocol[len(algorandGUIDProtocolPrefix):]) + telemetryInfoParts := strings.Split(telemetryInfo, "/") + if len(telemetryInfoParts) == 2 { + telemetryIDBytes, err := base32.StdEncoding.DecodeString(telemetryInfoParts[0]) + if err == nil { + telemetryID = string(telemetryIDBytes) + } + telemetryInstanceBytes, err := base32.StdEncoding.DecodeString(telemetryInfoParts[1]) + if err == nil { + telemetryInstance = string(telemetryInstanceBytes) + } + return telemetryID, telemetryInstance + } + } + } + return "", "" +} + +func formatPeerTelemetryInfoProtocolName(telemetryID string, telemetryInstance string) string { + return fmt.Sprintf(algorandGUIDProtocolTemplate, + base32.StdEncoding.EncodeToString([]byte(telemetryID)), + base32.StdEncoding.EncodeToString([]byte(telemetryInstance)), + ) +} diff --git a/network/p2p/p2p_test.go b/network/p2p/p2p_test.go index 558131fe48..fb14193a55 100644 --- a/network/p2p/p2p_test.go +++ b/network/p2p/p2p_test.go @@ -17,11 +17,22 @@ package p2p import ( + "context" "fmt" + "sync/atomic" "testing" + "time" - "github.com/algorand/go-algorand/test/partitiontest" + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/test/partitiontest" ) // Tests the helper function netAddressToListenAddress which converts @@ -74,3 +85,153 @@ func TestNetAddressToListenAddress(t *testing.T) { }) } } + +func TestP2PStreamingHost(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + dir := t.TempDir() + pstore, err := peerstore.NewPeerStore(nil, "") + require.NoError(t, err) + h, la, err := MakeHost(cfg, dir, pstore) + require.NoError(t, err) + + var h1calls atomic.Int64 + h1 := func(network.Stream) { + h1calls.Add(1) + } + var h2calls atomic.Int64 + h2 := func(network.Stream) { + h2calls.Add(1) + } + + ma, err := multiaddr.NewMultiaddr(la) + require.NoError(t, err) + h.Network().Listen(ma) + defer h.Close() + + h.SetStreamHandler(AlgorandWsProtocol, h1) + h.SetStreamHandler(AlgorandWsProtocol, h2) + + addrInfo := peer.AddrInfo{ + ID: h.ID(), + Addrs: h.Addrs(), + } + cpstore, err := peerstore.NewPeerStore([]*peer.AddrInfo{&addrInfo}, "") + require.NoError(t, err) + c, _, err := MakeHost(cfg, dir, cpstore) + require.NoError(t, err) + defer c.Close() + + s1, err := c.NewStream(context.Background(), h.ID(), AlgorandWsProtocol) + require.NoError(t, err) + s1.Write([]byte("hello")) + defer s1.Close() + + require.Eventually(t, func() bool { + return h1calls.Load() == 1 && h2calls.Load() == 1 + }, 5*time.Second, 100*time.Millisecond) + + // ensure a single handler also works as expected + h1calls.Store(0) + h.SetStreamHandler(algorandP2pHTTPProtocol, h1) + + s2, err := c.NewStream(context.Background(), h.ID(), algorandP2pHTTPProtocol) + require.NoError(t, err) + s2.Write([]byte("hello")) + defer s2.Close() + + require.Eventually(t, func() bool { + return h1calls.Load() == 1 + }, 5*time.Second, 100*time.Millisecond) + +} + +// TestP2PGetPeerTelemetryInfo tests the GetPeerTelemetryInfo function +func TestP2PGetPeerTelemetryInfo(t *testing.T) { + partitiontest.PartitionTest(t) + + testCases := []struct { + name string + peerProtocols []protocol.ID + expectedTelemetryID string + expectedTelemetryInstance string + }{ + { + name: "Valid Telemetry Info", + peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("telemetryID", "telemetryInstance"))}, + expectedTelemetryID: "telemetryID", + expectedTelemetryInstance: "telemetryInstance", + }, + { + name: "Partial Telemetry Info 1", + peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("telemetryID", ""))}, + expectedTelemetryID: "telemetryID", + expectedTelemetryInstance: "", + }, + { + name: "Partial Telemetry Info 2", + peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("", "telemetryInstance"))}, + expectedTelemetryID: "", + expectedTelemetryInstance: "telemetryInstance", + }, + { + name: "No Telemetry Info", + peerProtocols: []protocol.ID{protocol.ID("/some-other-protocol/1.0.0/otherID/otherInstance")}, + expectedTelemetryID: "", + expectedTelemetryInstance: "", + }, + { + name: "Invalid Telemetry Info Format", + peerProtocols: []protocol.ID{protocol.ID("/algorand-telemetry/1.0.0/invalidFormat")}, + expectedTelemetryID: "", + expectedTelemetryInstance: "", + }, + { + name: "Special Characters Telemetry Info Format", + peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("telemetry/ID", "123-//11-33"))}, + expectedTelemetryID: "telemetry/ID", + expectedTelemetryInstance: "123-//11-33", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + telemetryID, telemetryInstance := GetPeerTelemetryInfo(tc.peerProtocols) + if telemetryID != tc.expectedTelemetryID || telemetryInstance != tc.expectedTelemetryInstance { + t.Errorf("Expected telemetry ID: %s, telemetry instance: %s, but got telemetry ID: %s, telemetry instance: %s", + tc.expectedTelemetryID, tc.expectedTelemetryInstance, telemetryID, telemetryInstance) + } + }) + } +} + +func TestP2PProtocolAsMeta(t *testing.T) { + partitiontest.PartitionTest(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + h1, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + require.NoError(t, err) + defer h1.Close() + + h1TID := "telemetryID1" + h1Inst := "telemetryInstance2" + telemetryProtoInfo := formatPeerTelemetryInfoProtocolName(h1TID, h1Inst) + h1.SetStreamHandler(protocol.ID(telemetryProtoInfo), func(s network.Stream) { s.Close() }) + + h2, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + require.NoError(t, err) + defer h2.Close() + + err = h2.Connect(ctx, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()}) + require.NoError(t, err) + + protos, err := h2.Peerstore().GetProtocols(h1.ID()) + require.NoError(t, err) + + tid, inst := GetPeerTelemetryInfo(protos) + require.Equal(t, h1TID, tid) + require.Equal(t, h1Inst, inst) +} diff --git a/network/p2p/peerID.go b/network/p2p/peerID.go index f31d29ffb7..ca7526977b 100644 --- a/network/p2p/peerID.go +++ b/network/p2p/peerID.go @@ -25,6 +25,7 @@ import ( "path" "github.com/algorand/go-algorand/config" + algocrypto "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/util" "github.com/libp2p/go-libp2p/core/crypto" @@ -38,6 +39,10 @@ const DefaultPrivKeyPath = "peerIDPrivKey.pem" // PeerID is a string representation of a peer's public key, primarily used to avoid importing libp2p into packages that shouldn't need it type PeerID string +func (id PeerID) String() string { + return peer.ID(id).String() +} + // GetPrivKey manages loading and creation of private keys for network PeerIDs // It prioritizes, in this order: // 1. user supplied path to privKey @@ -104,3 +109,39 @@ func generatePrivKey() (crypto.PrivKey, error) { priv, _, err := crypto.GenerateEd25519Key(rand.Reader) return priv, err } + +// PeerIDChallengeSigner implements the identityChallengeSigner interface in the network package. +type PeerIDChallengeSigner struct { + key crypto.PrivKey +} + +// Sign implements the identityChallengeSigner interface. +func (p *PeerIDChallengeSigner) Sign(message algocrypto.Hashable) algocrypto.Signature { + return p.SignBytes(algocrypto.HashRep(message)) +} + +// SignBytes implements the identityChallengeSigner interface. +func (p *PeerIDChallengeSigner) SignBytes(message []byte) algocrypto.Signature { + // libp2p Ed25519PrivateKey.Sign() returns a signature with a length of 64 bytes and no error + sig, err := p.key.Sign(message) + if len(sig) != len(algocrypto.Signature{}) { + panic(fmt.Sprintf("invalid signature length: %d", len(sig))) + } + if err != nil { + panic(err) + } + return algocrypto.Signature(sig) +} + +// PublicKey implements the identityChallengeSigner interface. +func (p *PeerIDChallengeSigner) PublicKey() algocrypto.PublicKey { + // libp2p Ed25519PublicKey.Raw() returns a 32-byte public key and no error + pub, err := p.key.GetPublic().Raw() + if len(pub) != len(algocrypto.PublicKey{}) { + panic(fmt.Sprintf("invalid public key length: %d", len(pub))) + } + if err != nil { + panic(err) + } + return algocrypto.PublicKey(pub) +} diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go index fa572c5912..3eda0d3686 100644 --- a/network/p2p/peerstore/peerstore.go +++ b/network/p2p/peerstore/peerstore.go @@ -22,6 +22,8 @@ import ( "math/rand" "time" + "github.com/algorand/go-algorand/network/phonebook" + "github.com/algorand/go-deadlock" "github.com/libp2p/go-libp2p/core/peer" libp2p "github.com/libp2p/go-libp2p/core/peerstore" mempstore "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" @@ -32,12 +34,6 @@ import ( // of how many addresses the phonebook actually has. ( with the retry-after logic applied ) const getAllAddresses = math.MaxInt32 -// PhoneBookEntryRoles defines the roles that a single entry on the phonebook can take. -// currently, we have two roles : relay role and archiver role, which are mutually exclusive. -// -//msgp:ignore PhoneBookEntryRoles -type PhoneBookEntryRoles int - const addressDataKey string = "addressData" // PeerStore implements Peerstore and CertifiedAddrBook. @@ -58,9 +54,10 @@ type addressData struct { // networkNames: lists the networks to which the given address belongs. networkNames map[string]bool + mu *deadlock.RWMutex // role is the role that this address serves. - role PhoneBookEntryRoles + role phonebook.PhoneBookEntryRoles // persistent is set true for peers whose record should not be removed for the peer list persistent bool @@ -73,18 +70,20 @@ type peerStoreCAB interface { } // NewPeerStore creates a new peerstore backed by a datastore. -func NewPeerStore(addrInfo []*peer.AddrInfo) (*PeerStore, error) { +func NewPeerStore(addrInfo []*peer.AddrInfo, network string) (*PeerStore, error) { ps, err := mempstore.NewPeerstore() if err != nil { return nil, fmt.Errorf("cannot initialize a peerstore: %w", err) } // initialize peerstore with addresses + peers := make([]interface{}, len(addrInfo)) for i := 0; i < len(addrInfo); i++ { - info := addrInfo[i] - ps.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) + peers[i] = addrInfo[i] } + pstore := &PeerStore{peerStoreCAB: ps} + pstore.AddPersistentPeers(peers, network, phonebook.PhoneBookEntryRelayRole) return pstore, nil } @@ -103,13 +102,13 @@ func MakePhonebook(connectionsRateLimitingCount uint, } // GetAddresses returns up to N addresses, but may return fewer -func (ps *PeerStore) GetAddresses(n int, role PhoneBookEntryRoles) []string { +func (ps *PeerStore) GetAddresses(n int, role phonebook.PhoneBookEntryRoles) []interface{} { return shuffleSelect(ps.filterRetryTime(time.Now(), role), n) } // UpdateRetryAfter updates the retryAfter time for the given address. func (ps *PeerStore) UpdateRetryAfter(addr string, retryAfter time.Time) { - info, err := PeerInfoFromDomainPort(addr) + info, err := peerInfoFromDomainPort(addr) if err != nil { return } @@ -130,15 +129,12 @@ func (ps *PeerStore) UpdateRetryAfter(addr string, retryAfter time.Time) { // The connection should be established when the waitTime is 0. // It will register a provisional next connection time when the waitTime is 0. // The provisional time should be updated after the connection with UpdateConnectionTime -func (ps *PeerStore) GetConnectionWaitTime(addr string) (bool, time.Duration, time.Time) { +func (ps *PeerStore) GetConnectionWaitTime(addrOrPeerID string) (bool, time.Duration, time.Time) { curTime := time.Now() - info, err := PeerInfoFromDomainPort(addr) - if err != nil { - return false, 0 /* not used */, curTime /* not used */ - } var timeSince time.Duration var numElmtsToRemove int - metadata, err := ps.Get(info.ID, addressDataKey) + peerID := peer.ID(addrOrPeerID) + metadata, err := ps.Get(peerID, addressDataKey) if err != nil { return false, 0 /* not used */, curTime /* not used */ } @@ -157,9 +153,9 @@ func (ps *PeerStore) GetConnectionWaitTime(addr string) (bool, time.Duration, ti } // Remove the expired elements from e.data[addr].recentConnectionTimes - ps.popNElements(numElmtsToRemove, peer.ID(addr)) + ps.popNElements(numElmtsToRemove, peerID) // If there are max number of connections within the time window, wait - metadata, _ = ps.Get(info.ID, addressDataKey) + metadata, _ = ps.Get(peerID, addressDataKey) ad, ok = metadata.(addressData) if !ok { return false, 0 /* not used */, curTime /* not used */ @@ -175,17 +171,14 @@ func (ps *PeerStore) GetConnectionWaitTime(addr string) (bool, time.Duration, ti // Update curTime, since it may have significantly changed if waited provisionalTime := time.Now() // Append the provisional time for the next connection request - ps.appendTime(info.ID, provisionalTime) + ps.appendTime(peerID, provisionalTime) return true, 0 /* no wait. proceed */, provisionalTime } // UpdateConnectionTime updates the connection time for the given address. -func (ps *PeerStore) UpdateConnectionTime(addr string, provisionalTime time.Time) bool { - info, err := PeerInfoFromDomainPort(addr) - if err != nil { - return false - } - metadata, err := ps.Get(info.ID, addressDataKey) +func (ps *PeerStore) UpdateConnectionTime(addrOrPeerID string, provisionalTime time.Time) bool { + peerID := peer.ID(addrOrPeerID) + metadata, err := ps.Get(peerID, addressDataKey) if err != nil { return false } @@ -194,7 +187,7 @@ func (ps *PeerStore) UpdateConnectionTime(addr string, provisionalTime time.Time return false } defer func() { - _ = ps.Put(info.ID, addressDataKey, ad) + _ = ps.Put(peerID, addressDataKey, ad) }() @@ -217,7 +210,7 @@ func (ps *PeerStore) UpdateConnectionTime(addr string, provisionalTime time.Time } // ReplacePeerList replaces the peer list for the given networkName and role. -func (ps *PeerStore) ReplacePeerList(addressesThey []string, networkName string, role PhoneBookEntryRoles) { +func (ps *PeerStore) ReplacePeerList(addressesThey []interface{}, networkName string, role phonebook.PhoneBookEntryRoles) { // prepare a map of items we'd like to remove. removeItems := make(map[peer.ID]bool, 0) peerIDs := ps.Peers() @@ -225,23 +218,24 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []string, networkName string, data, _ := ps.Get(pid, addressDataKey) if data != nil { ad := data.(addressData) + ad.mu.RLock() if ad.networkNames[networkName] && ad.role == role && !ad.persistent { removeItems[pid] = true } + ad.mu.RUnlock() } } for _, addr := range addressesThey { - info, err := PeerInfoFromDomainPort(addr) - if err != nil { - return - } + info := addr.(*peer.AddrInfo) data, _ := ps.Get(info.ID, addressDataKey) if data != nil { // we already have this. // Update the networkName ad := data.(addressData) + ad.mu.Lock() ad.networkNames[networkName] = true + ad.mu.Unlock() // do not remove this entry delete(removeItems, info.ID) @@ -261,13 +255,9 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []string, networkName string, // AddPersistentPeers stores addresses of peers which are persistent. // i.e. they won't be replaced by ReplacePeerList calls -func (ps *PeerStore) AddPersistentPeers(dnsAddresses []string, networkName string, role PhoneBookEntryRoles) { - +func (ps *PeerStore) AddPersistentPeers(dnsAddresses []interface{}, networkName string, role phonebook.PhoneBookEntryRoles) { for _, addr := range dnsAddresses { - info, err := PeerInfoFromDomainPort(addr) - if err != nil { - return - } + info := addr.(*peer.AddrInfo) data, _ := ps.Get(info.ID, addressDataKey) if data != nil { // we already have this. @@ -291,9 +281,10 @@ func (ps *PeerStore) Length() int { } // makePhonebookEntryData creates a new address entry for provided network name and role. -func makePhonebookEntryData(networkName string, role PhoneBookEntryRoles, persistent bool) addressData { +func makePhonebookEntryData(networkName string, role phonebook.PhoneBookEntryRoles, persistent bool) addressData { pbData := addressData{ networkNames: make(map[string]bool), + mu: &deadlock.RWMutex{}, recentConnectionTimes: make([]time.Time, 0), role: role, persistent: persistent, @@ -308,8 +299,11 @@ func (ps *PeerStore) deletePhonebookEntry(peerID peer.ID, networkName string) { return } ad := data.(addressData) + ad.mu.Lock() delete(ad.networkNames, networkName) - if 0 == len(ad.networkNames) { + isEmpty := len(ad.networkNames) == 0 + ad.mu.Unlock() + if isEmpty { ps.ClearAddrs(peerID) _ = ps.Put(peerID, addressDataKey, nil) } @@ -334,21 +328,23 @@ func (ps *PeerStore) popNElements(n int, peerID peer.ID) { _ = ps.Put(peerID, addressDataKey, ad) } -func (ps *PeerStore) filterRetryTime(t time.Time, role PhoneBookEntryRoles) []string { - o := make([]string, 0, len(ps.Peers())) +func (ps *PeerStore) filterRetryTime(t time.Time, role phonebook.PhoneBookEntryRoles) []interface{} { + o := make([]interface{}, 0, len(ps.Peers())) for _, peerID := range ps.Peers() { data, _ := ps.Get(peerID, addressDataKey) if data != nil { ad := data.(addressData) if t.After(ad.retryAfter) && role == ad.role { - o = append(o, string(peerID)) + mas := ps.Addrs(peerID) + info := peer.AddrInfo{ID: peerID, Addrs: mas} + o = append(o, &info) } } } return o } -func shuffleSelect(set []string, n int) []string { +func shuffleSelect(set []interface{}, n int) []interface{} { if n >= len(set) || n == getAllAddresses { // return shuffled copy of everything out := slices.Clone(set) @@ -365,13 +361,13 @@ func shuffleSelect(set []string, n int) []string { } } } - out := make([]string, n) + out := make([]interface{}, n) for i, index := range indexSample { out[i] = set[index] } return out } -func shuffleStrings(set []string) { +func shuffleStrings(set []interface{}) { rand.Shuffle(len(set), func(i, j int) { set[i], set[j] = set[j], set[i] }) } diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go index 9bbf2b87c6..e855013d76 100644 --- a/network/p2p/peerstore/peerstore_test.go +++ b/network/p2p/peerstore/peerstore_test.go @@ -23,11 +23,13 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" libp2p_crypto "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" libp2p "github.com/libp2p/go-libp2p/core/peerstore" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/network/phonebook" + "github.com/algorand/go-algorand/test/partitiontest" ) // PhoneBookEntryRelayRole used for all the relays that are provided either via the algobootstrap SRV record @@ -49,7 +51,7 @@ func TestPeerstore(t *testing.T) { } addrInfo, _ := PeerInfoFromAddrs(peerAddrs) - ps, err := NewPeerStore(addrInfo) + ps, err := NewPeerStore(addrInfo, "net-id") require.NoError(t, err) defer ps.Close() @@ -87,12 +89,13 @@ func TestPeerstore(t *testing.T) { } -func testPhonebookAll(t *testing.T, set []string, ph *PeerStore) { +func testPhonebookAll(t *testing.T, set []*peer.AddrInfo, ph *PeerStore) { actual := ph.GetAddresses(len(set), PhoneBookEntryRelayRole) for _, got := range actual { + info := got.(*peer.AddrInfo) ok := false for _, known := range set { - if got == known { + if info.ID == known.ID { ok = true break } @@ -104,7 +107,8 @@ func testPhonebookAll(t *testing.T, set []string, ph *PeerStore) { for _, known := range set { ok := false for _, got := range actual { - if got == known { + info := got.(*peer.AddrInfo) + if info.ID == known.ID { ok = true break } @@ -115,18 +119,19 @@ func testPhonebookAll(t *testing.T, set []string, ph *PeerStore) { } } -func testPhonebookUniform(t *testing.T, set []string, ph *PeerStore, getsize int) { +func testPhonebookUniform(t *testing.T, set []*peer.AddrInfo, ph *PeerStore, getsize int) { uniformityTestLength := 250000 / len(set) expected := (uniformityTestLength * getsize) / len(set) counts := make(map[string]int) for i := 0; i < len(set); i++ { - counts[set[i]] = 0 + counts[set[i].ID.String()] = 0 } for i := 0; i < uniformityTestLength; i++ { actual := ph.GetAddresses(getsize, PhoneBookEntryRelayRole) for _, xa := range actual { - if _, ok := counts[xa]; ok { - counts[xa]++ + info := xa.(*peer.AddrInfo) + if _, ok := counts[info.ID.String()]; ok { + counts[info.ID.String()]++ } } } @@ -149,57 +154,84 @@ func TestArrayPhonebookAll(t *testing.T) { partitiontest.PartitionTest(t) set := []string{"a:4041", "b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} + infoSet := make([]*peer.AddrInfo, 0) + for _, addr := range set { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoSet = append(infoSet, info) + } + ph, err := MakePhonebook(1, 1*time.Millisecond) require.NoError(t, err) for _, addr := range set { entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false) - info, _ := PeerInfoFromDomainPort(addr) + info, _ := peerInfoFromDomainPort(addr) ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) ph.Put(info.ID, addressDataKey, entry) } - testPhonebookAll(t, set, ph) + testPhonebookAll(t, infoSet, ph) } func TestArrayPhonebookUniform1(t *testing.T) { partitiontest.PartitionTest(t) set := []string{"a:4041", "b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} + infoSet := make([]*peer.AddrInfo, 0) + for _, addr := range set { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoSet = append(infoSet, info) + } + ph, err := MakePhonebook(1, 1*time.Millisecond) require.NoError(t, err) for _, addr := range set { entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false) - info, _ := PeerInfoFromDomainPort(addr) + info, _ := peerInfoFromDomainPort(addr) ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) ph.Put(info.ID, addressDataKey, entry) } - testPhonebookUniform(t, set, ph, 1) + testPhonebookUniform(t, infoSet, ph, 1) } func TestArrayPhonebookUniform3(t *testing.T) { partitiontest.PartitionTest(t) set := []string{"a:4041", "b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} + infoSet := make([]*peer.AddrInfo, 0) + for _, addr := range set { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoSet = append(infoSet, info) + } + ph, err := MakePhonebook(1, 1*time.Millisecond) require.NoError(t, err) for _, addr := range set { entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false) - info, _ := PeerInfoFromDomainPort(addr) + info, _ := peerInfoFromDomainPort(addr) ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL) ph.Put(info.ID, addressDataKey, entry) } - testPhonebookUniform(t, set, ph, 3) + testPhonebookUniform(t, infoSet, ph, 3) } func TestMultiPhonebook(t *testing.T) { partitiontest.PartitionTest(t) set := []string{"a:4041", "b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} - pha := make([]string, 0) - for _, e := range set[:5] { + infoSet := make([]*peer.AddrInfo, 0) + for _, addr := range set { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoSet = append(infoSet, info) + } + pha := make([]interface{}, 0) + for _, e := range infoSet[:5] { pha = append(pha, e) } - phb := make([]string, 0) - for _, e := range set[5:] { + phb := make([]interface{}, 0) + for _, e := range infoSet[5:] { phb = append(phb, e) } @@ -208,9 +240,9 @@ func TestMultiPhonebook(t *testing.T) { ph.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole) ph.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole) - testPhonebookAll(t, set, ph) - testPhonebookUniform(t, set, ph, 1) - testPhonebookUniform(t, set, ph, 3) + testPhonebookAll(t, infoSet, ph) + testPhonebookUniform(t, infoSet, ph, 1) + testPhonebookUniform(t, infoSet, ph, 3) } // TestMultiPhonebookPersistentPeers validates that the peers added via Phonebook.AddPersistentPeers @@ -218,14 +250,23 @@ func TestMultiPhonebook(t *testing.T) { func TestMultiPhonebookPersistentPeers(t *testing.T) { partitiontest.PartitionTest(t) - persistentPeers := []string{"a:4041"} + info, err := peerInfoFromDomainPort("a:4041") + require.NoError(t, err) + persistentPeers := []interface{}{info} set := []string{"b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} - pha := make([]string, 0) - for _, e := range set[:5] { + infoSet := make([]*peer.AddrInfo, 0) + for _, addr := range set { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoSet = append(infoSet, info) + } + + pha := make([]interface{}, 0) + for _, e := range infoSet[:5] { pha = append(pha, e) } - phb := make([]string, 0) - for _, e := range set[5:] { + phb := make([]interface{}, 0) + for _, e := range infoSet[5:] { phb = append(phb, e) } ph, err := MakePhonebook(1, 1*time.Millisecond) @@ -235,10 +276,19 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) { ph.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole) ph.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole) - testPhonebookAll(t, append(set, persistentPeers...), ph) + testPhonebookAll(t, append(infoSet, info), ph) allAddresses := ph.GetAddresses(len(set)+len(persistentPeers), PhoneBookEntryRelayRole) for _, pp := range persistentPeers { - require.Contains(t, allAddresses, pp) + pp := pp.(*peer.AddrInfo) + found := false + for _, addr := range allAddresses { + addr := addr.(*peer.AddrInfo) + if addr.ID == pp.ID { + found = true + break + } + } + require.True(t, found, fmt.Sprintf("%s not found in %v", string(pp.ID), allAddresses)) } } @@ -246,12 +296,19 @@ func TestMultiPhonebookDuplicateFiltering(t *testing.T) { partitiontest.PartitionTest(t) set := []string{"b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} - pha := make([]string, 0) - for _, e := range set[:7] { + infoSet := make([]*peer.AddrInfo, 0) + for _, addr := range set { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoSet = append(infoSet, info) + } + + pha := make([]interface{}, 0) + for _, e := range infoSet[:7] { pha = append(pha, e) } - phb := make([]string, 0) - for _, e := range set[3:] { + phb := make([]interface{}, 0) + for _, e := range infoSet[3:] { phb = append(phb, e) } ph, err := MakePhonebook(1, 1*time.Millisecond) @@ -259,9 +316,9 @@ func TestMultiPhonebookDuplicateFiltering(t *testing.T) { ph.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole) ph.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole) - testPhonebookAll(t, set, ph) - testPhonebookUniform(t, set, ph, 1) - testPhonebookUniform(t, set, ph, 3) + testPhonebookAll(t, infoSet, ph) + testPhonebookUniform(t, infoSet, ph, 1) + testPhonebookUniform(t, infoSet, ph, 3) } func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { @@ -276,21 +333,21 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { require.NoError(t, err) addr1 := "addrABC:4040" addr2 := "addrXYZ:4041" - info1, _ := PeerInfoFromDomainPort(addr1) - info2, _ := PeerInfoFromDomainPort(addr2) + info1, _ := peerInfoFromDomainPort(addr1) + info2, _ := peerInfoFromDomainPort(addr2) // Address not in. Should return false - addrInPhonebook, _, provisionalTime := entries.GetConnectionWaitTime(addr1) + addrInPhonebook, _, provisionalTime := entries.GetConnectionWaitTime(string(info1.ID)) require.Equal(t, false, addrInPhonebook) - require.Equal(t, false, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, false, entries.UpdateConnectionTime(string(info1.ID), provisionalTime)) // Test the addresses are populated in the phonebook and a // time can be added to one of them - entries.ReplacePeerList([]string{addr1, addr2}, "default", PhoneBookEntryRelayRole) - addrInPhonebook, waitTime, provisionalTime := entries.GetConnectionWaitTime(addr1) + entries.ReplacePeerList([]interface{}{info1, info2}, "default", PhoneBookEntryRelayRole) + addrInPhonebook, waitTime, provisionalTime := entries.GetConnectionWaitTime(string(info1.ID)) require.Equal(t, true, addrInPhonebook) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(string(info1.ID), provisionalTime)) data, _ := entries.Get(info1.ID, addressDataKey) require.NotNil(t, data) ad := data.(addressData) @@ -303,9 +360,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { } // add another value to addr - addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1) + addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info1.ID)) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(string(info1.ID), provisionalTime)) data, _ = entries.Get(info1.ID, addressDataKey) ad = data.(addressData) phBookData = ad.recentConnectionTimes @@ -318,9 +375,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { // the first time should be removed and a new one added // there should not be any wait - addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1) + addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info1.ID)) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(string(info1.ID), provisionalTime)) data, _ = entries.Get(info1.ID, addressDataKey) ad = data.(addressData) phBookData2 := ad.recentConnectionTimes @@ -335,9 +392,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { // add 3 values to another address. should not wait // value 1 - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info2.ID)) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(string(info2.ID), provisionalTime)) // introduce a gap between the two requests so that only the first will be removed later when waited // simulate passing a unit of time @@ -349,13 +406,13 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { } // value 2 - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info2.ID)) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(string(info2.ID), provisionalTime)) // value 3 - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info2.ID)) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(string(info2.ID), provisionalTime)) data2, _ = entries.Get(info2.ID, addressDataKey) ad2 = data2.(addressData) @@ -364,7 +421,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { require.Equal(t, 3, len(phBookData)) // add another element to trigger wait - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info2.ID)) require.Greater(t, int64(waitTime), int64(0)) // no element should be removed data2, _ = entries.Get(info2.ID, addressDataKey) @@ -379,9 +436,9 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { } // The wait should be sufficient - _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2) + _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info2.ID)) require.Equal(t, time.Duration(0), waitTime) - require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime)) + require.Equal(t, true, entries.UpdateConnectionTime(string(info2.ID), provisionalTime)) // only one element should be removed, and one added data2, _ = entries.Get(info2.ID, addressDataKey) ad2 = data2.(addressData) @@ -401,24 +458,40 @@ func TestPhonebookRoles(t *testing.T) { relaysSet := []string{"relay1:4040", "relay2:4041", "relay3:4042"} archiverSet := []string{"archiver1:1111", "archiver2:1112", "archiver3:1113"} + infoRelaySet := make([]interface{}, 0) + for _, addr := range relaysSet { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoRelaySet = append(infoRelaySet, info) + } + + infoArchiverSet := make([]interface{}, 0) + for _, addr := range archiverSet { + info, err := peerInfoFromDomainPort(addr) + require.NoError(t, err) + infoArchiverSet = append(infoArchiverSet, info) + } + ph, err := MakePhonebook(1, 1) require.NoError(t, err) - ph.ReplacePeerList(relaysSet, "default", PhoneBookEntryRelayRole) - ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchiverRole) + ph.ReplacePeerList(infoRelaySet, "default", PhoneBookEntryRelayRole) + ph.ReplacePeerList(infoArchiverSet, "default", PhoneBookEntryArchiverRole) require.Equal(t, len(relaysSet)+len(archiverSet), len(ph.Peers())) require.Equal(t, len(relaysSet)+len(archiverSet), ph.Length()) - for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} { + for _, role := range []phonebook.PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} { for k := 0; k < 100; k++ { for l := 0; l < 3; l++ { entries := ph.GetAddresses(l, role) if role == PhoneBookEntryRelayRole { for _, entry := range entries { - require.Contains(t, entry, "relay") + entry := entry.(*peer.AddrInfo) + require.Contains(t, string(entry.ID), "relay") } } else if role == PhoneBookEntryArchiverRole { for _, entry := range entries { - require.Contains(t, entry, "archiver") + entry := entry.(*peer.AddrInfo) + require.Contains(t, string(entry.ID), "archiver") } } } diff --git a/network/p2p/peerstore/utils.go b/network/p2p/peerstore/utils.go index 02c6b2d8e6..90b0af497c 100644 --- a/network/p2p/peerstore/utils.go +++ b/network/p2p/peerstore/utils.go @@ -53,8 +53,8 @@ func PeerInfoFromAddr(addr string) (*peer.AddrInfo, error) { return info, nil } -// PeerInfoFromDomainPort converts a string of the form domain:port to AddrInfo -func PeerInfoFromDomainPort(domainPort string) (*peer.AddrInfo, error) { +// peerInfoFromDomainPort converts a string of the form domain:port to AddrInfo +func peerInfoFromDomainPort(domainPort string) (*peer.AddrInfo, error) { parts := strings.Split(domainPort, ":") if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return nil, fmt.Errorf("invalid domain port string %s, found %d colon-separated parts", domainPort, len(parts)) diff --git a/network/p2p/peerstore/utils_test.go b/network/p2p/peerstore/utils_test.go index 9f20b0f75b..d0d67f4fe0 100644 --- a/network/p2p/peerstore/utils_test.go +++ b/network/p2p/peerstore/utils_test.go @@ -20,8 +20,9 @@ import ( "fmt" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestPeerInfoFromAddr(t *testing.T) { diff --git a/network/p2p/pubsub.go b/network/p2p/pubsub.go index 372c9249c8..a968bcb6a9 100644 --- a/network/p2p/pubsub.go +++ b/network/p2p/pubsub.go @@ -53,6 +53,8 @@ const ( // TXTopicName defines a pubsub topic for TX messages const TXTopicName = "/algo/tx/0.1.0" +const incomingThreads = 20 // matches to number wsNetwork workers + func makePubSub(ctx context.Context, cfg config.Local, host host.Host) (*pubsub.PubSub, error) { //defaultParams := pubsub.DefaultGossipSubParams() @@ -93,7 +95,9 @@ func makePubSub(ctx context.Context, cfg config.Local, host host.Host) (*pubsub. pubsub.WithSubscriptionFilter(pubsub.WrapLimitSubscriptionFilter(pubsub.NewAllowlistSubscriptionFilter(TXTopicName), 100)), // pubsub.WithEventTracer(jsonTracer), pubsub.WithValidateQueueSize(256), + pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign), // pubsub.WithValidateThrottle(cfg.TxBacklogSize), + pubsub.WithValidateWorkers(incomingThreads), } return pubsub.NewGossipSub(ctx, host, options...) @@ -133,7 +137,7 @@ func (s *serviceImpl) getOrCreateTopic(topicName string) (*pubsub.Topic, error) } // Subscribe returns a subscription to the given topic -func (s *serviceImpl) Subscribe(topic string, val pubsub.ValidatorEx) (*pubsub.Subscription, error) { +func (s *serviceImpl) Subscribe(topic string, val pubsub.ValidatorEx) (SubNextCancellable, error) { if err := s.pubsub.RegisterTopicValidator(topic, val); err != nil { return nil, err } diff --git a/network/p2p/streams.go b/network/p2p/streams.go index 4a7a2d8e01..d16633adfd 100644 --- a/network/p2p/streams.go +++ b/network/p2p/streams.go @@ -73,7 +73,17 @@ func (n *streamManager) streamHandler(stream network.Stream) { n.log.Infof("Failed to check old stream with %s: %v", remotePeer, err) } n.streams[stream.Conn().RemotePeer()] = stream - n.handler(n.ctx, remotePeer, stream, true) + + // streamHandler is supposed to be called for accepted streams, so we expect incoming here + incoming := stream.Conn().Stat().Direction == network.DirInbound + if !incoming { + if stream.Stat().Direction == network.DirUnknown { + n.log.Warnf("Unknown direction for a steam %s to/from %s", stream.ID(), remotePeer) + } else { + n.log.Warnf("Unexpected outgoing stream in streamHandler for connection %s (%s): %s vs %s stream", stream.Conn().ID(), remotePeer, stream.Conn().Stat().Direction, stream.Stat().Direction.String()) + } + } + n.handler(n.ctx, remotePeer, stream, incoming) return } // otherwise, the old stream is still open, so we can close the new one @@ -82,7 +92,30 @@ func (n *streamManager) streamHandler(stream network.Stream) { } // no old stream n.streams[stream.Conn().RemotePeer()] = stream - n.handler(n.ctx, remotePeer, stream, true) + // streamHandler is supposed to be called for accepted streams, so we expect incoming here + incoming := stream.Conn().Stat().Direction == network.DirInbound + if !incoming { + if stream.Stat().Direction == network.DirUnknown { + n.log.Warnf("streamHandler: unknown direction for a steam %s to/from %s", stream.ID(), remotePeer) + } else { + n.log.Warnf("Unexpected outgoing stream in streamHandler for connection %s (%s): %s vs %s stream", stream.Conn().ID(), remotePeer, stream.Conn().Stat().Direction, stream.Stat().Direction.String()) + } + } + n.handler(n.ctx, remotePeer, stream, incoming) +} + +// streamHandlerHTTP tracks the ProtocolIDForMultistreamSelect = "/http/1.1" streams +func (n *streamManager) streamHandlerHTTP(stream network.Stream) { + n.streamsLock.Lock() + defer n.streamsLock.Unlock() + n.streams[stream.Conn().LocalPeer()] = stream +} + +func (n *streamManager) getStream(peerID peer.ID) (network.Stream, bool) { + n.streamsLock.Lock() + defer n.streamsLock.Unlock() + stream, ok := n.streams[peerID] + return stream, ok } // Connected is called when a connection is opened @@ -95,8 +128,13 @@ func (n *streamManager) Connected(net network.Network, conn network.Conn) { return } + needUnlock := true n.streamsLock.Lock() - defer n.streamsLock.Unlock() + defer func() { + if needUnlock { + n.streamsLock.Unlock() + } + }() _, ok := n.streams[remotePeer] if ok { return // there's already an active stream with this peer for our protocol @@ -104,12 +142,26 @@ func (n *streamManager) Connected(net network.Network, conn network.Conn) { stream, err := n.host.NewStream(n.ctx, remotePeer, AlgorandWsProtocol) if err != nil { - n.log.Infof("Failed to open stream to %s: %v", remotePeer, err) + n.log.Infof("Failed to open stream to %s (%s): %v", remotePeer, conn.RemoteMultiaddr().String(), err) return } - n.streams[remotePeer] = stream - n.handler(n.ctx, remotePeer, stream, false) + + // release the lock to let handler do its thing + // otherwise reading/writing to the stream will deadlock + needUnlock = false + n.streamsLock.Unlock() + + // a new stream created above, expected direction is outbound + incoming := stream.Conn().Stat().Direction == network.DirInbound + if incoming { + n.log.Warnf("Unexpected incoming stream in streamHandler for connection %s (%s): %s vs %s stream", stream.Conn().ID(), remotePeer, stream.Conn().Stat().Direction, stream.Stat().Direction.String()) + } else { + if stream.Stat().Direction == network.DirUnknown { + n.log.Warnf("Connected: unknown direction for a steam %s to/from %s", stream.ID(), remotePeer) + } + } + n.handler(n.ctx, remotePeer, stream, incoming) } // Disconnected is called when a connection is closed @@ -122,6 +174,12 @@ func (n *streamManager) Disconnected(net network.Network, conn network.Conn) { stream.Close() delete(n.streams, conn.RemotePeer()) } + + stream, ok = n.streams[conn.LocalPeer()] + if ok { + stream.Close() + delete(n.streams, conn.LocalPeer()) + } } // Listen is called when network starts listening on an addr diff --git a/network/p2p/testing/httpNode.go b/network/p2p/testing/httpNode.go new file mode 100644 index 0000000000..523cdc5d4c --- /dev/null +++ b/network/p2p/testing/httpNode.go @@ -0,0 +1,122 @@ +// Copyright (C) 2019-2024 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 . + +// This package wraps and re-exports the libp2p functions on order to keep +// all go-libp2p imports in one place. + +package p2p + +import ( + "net/http" + "testing" + + "github.com/algorand/go-algorand/components/mocks" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/p2p" + "github.com/libp2p/go-libp2p" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +// HTTPNode is a mock network node that uses libp2p and http. +type HTTPNode struct { + mocks.MockNetwork + host.Host + httpServer *p2p.HTTPServer + peers []network.Peer + tb testing.TB + genesisID string +} + +// MakeHTTPNode returns a new P2PHTTPNode node. +func MakeHTTPNode(tb testing.TB) *HTTPNode { + p2pHost, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0")) + require.NoError(tb, err) + + return &HTTPNode{ + Host: p2pHost, + httpServer: p2p.MakeHTTPServer(p2pHost), + tb: tb, + } +} + +// RegisterHTTPHandler registers a http handler with a given path. +func (p *HTTPNode) RegisterHTTPHandler(path string, handler http.Handler) { + p.httpServer.RegisterHTTPHandler(path, handler) +} + +// RegisterHandlers not implemented. +func (p *HTTPNode) RegisterHandlers(dispatch []network.TaggedMessageHandler) {} + +// Start starts http service +func (p *HTTPNode) Start() error { + go func() { + err := p.httpServer.Serve() + require.NoError(p.tb, err) + }() + return nil +} + +// Stop stops http service +func (p *HTTPNode) Stop() { + p.httpServer.Close() + p.Host.Close() +} + +// GetHTTPPeer returns the http peer for connecting to this node +func (p *HTTPNode) GetHTTPPeer() network.Peer { + addrInfo := peer.AddrInfo{ID: p.ID(), Addrs: p.Addrs()} + return httpPeer{addrInfo, p.tb} +} + +// GetGenesisID returns genesisID +func (p *HTTPNode) GetGenesisID() string { return p.genesisID } + +// SetGenesisID sets genesisID +func (p *HTTPNode) SetGenesisID(genesisID string) { p.genesisID = genesisID } + +type httpPeer struct { + addrInfo peer.AddrInfo + tb testing.TB +} + +// GetAddress implements HTTPPeer interface returns the address of the peer +func (p httpPeer) GetAddress() string { + mas, err := peer.AddrInfoToP2pAddrs(&p.addrInfo) + require.NoError(p.tb, err) + require.Len(p.tb, mas, 1) + return mas[0].String() +} + +// GetAddress implements HTTPPeer interface and returns the http client for a peer +func (p httpPeer) GetHTTPClient() *http.Client { + c, err := p2p.MakeHTTPClient(&p.addrInfo) + require.NoError(p.tb, err) + return c +} + +// SetPeers sets peers +func (p *HTTPNode) SetPeers(other *HTTPNode) { + addrInfo := peer.AddrInfo{ID: other.ID(), Addrs: other.Addrs()} + hpeer := httpPeer{addrInfo, p.tb} + p.peers = append(p.peers, hpeer) +} + +// GetPeers returns peers +func (p *HTTPNode) GetPeers(options ...network.PeerOption) []network.Peer { + return p.peers +} diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 6301b1b521..7ebbb5a665 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -18,6 +18,7 @@ package network import ( "context" + "math/rand" "net" "net/http" "strings" @@ -27,14 +28,20 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network/limitcaller" "github.com/algorand/go-algorand/network/p2p" + "github.com/algorand/go-algorand/network/p2p/dnsaddr" "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-deadlock" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/discovery/backoff" + "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" ) @@ -59,17 +66,156 @@ type P2PNetwork struct { handler msgHandler broadcaster msgBroadcaster wsPeers map[peer.ID]*wsPeer + wsPeersToIDs map[*wsPeer]peer.ID wsPeersLock deadlock.RWMutex wsPeersChangeCounter atomic.Int32 wsPeersConnectivityCheckTicker *time.Ticker + peerStater peerConnectionStater + + relayMessages bool // True if we should relay messages from other nodes (nominally true for relays, false otherwise) + wantTXGossip atomic.Bool + + capabilitiesDiscovery *p2p.CapabilitiesDiscovery + + bootstrapperStart func() + bootstrapperStop func() + nodeInfo NodeInfo + pstore *peerstore.PeerStore + httpServer *p2p.HTTPServer +} + +type bootstrapper struct { + cfg config.Local + networkID protocol.NetworkID + phonebookPeers []*peer.AddrInfo + resolveController dnsaddr.ResolveController + started atomic.Bool + log logging.Logger +} + +func (b *bootstrapper) start() { + b.started.Store(true) +} + +func (b *bootstrapper) stop() { + b.started.Store(false) +} + +func (b *bootstrapper) BootstrapFunc() []peer.AddrInfo { + // not started yet, do not give it any peers + if !b.started.Load() { + return nil + } + + // have a list of peers, use them + if len(b.phonebookPeers) > 0 { + var addrs []peer.AddrInfo + for _, bPeer := range b.phonebookPeers { + if bPeer != nil { + addrs = append(addrs, *bPeer) + } + } + return addrs + } + + return dnsLookupBootstrapPeers(b.log, b.cfg, b.networkID, b.resolveController) +} + +// dnsLookupBootstrapPeers looks up a list of Multiaddrs strings from the dnsaddr records at the primary +// SRV record domain. +func dnsLookupBootstrapPeers(log logging.Logger, cfg config.Local, network protocol.NetworkID, controller dnsaddr.ResolveController) []peer.AddrInfo { + var addrs []peer.AddrInfo + bootstraps := cfg.DNSBootstrapArray(network) + for _, dnsBootstrap := range bootstraps { + var resolvedAddrs, resolvedAddrsBackup []multiaddr.Multiaddr + var errPrim, errBackup error + resolvedAddrs, errPrim = dnsaddr.MultiaddrsFromResolver(dnsBootstrap.PrimarySRVBootstrap, controller) + if errPrim != nil { + log.Infof("Failed to resolve bootstrap peers from %s: %v", dnsBootstrap.PrimarySRVBootstrap, errPrim) + } + if dnsBootstrap.BackupSRVBootstrap != "" { + resolvedAddrsBackup, errBackup = dnsaddr.MultiaddrsFromResolver(dnsBootstrap.BackupSRVBootstrap, controller) + if errBackup != nil { + log.Infof("Failed to resolve bootstrap peers from %s: %v", dnsBootstrap.BackupSRVBootstrap, errBackup) + } + } + + if len(resolvedAddrs) > 0 || len(resolvedAddrsBackup) > 0 { + resolvedAddrInfos := mergeP2PMultiaddrResolvedAddresses(resolvedAddrs, resolvedAddrsBackup) + addrs = append(addrs, resolvedAddrInfos...) + } + } + return addrs +} + +func mergeP2PMultiaddrResolvedAddresses(primary, backup []multiaddr.Multiaddr) []peer.AddrInfo { + // deduplicate addresses by PeerID + unique := make(map[peer.ID]*peer.AddrInfo) + for _, addr := range primary { + info, err0 := peer.AddrInfoFromP2pAddr(addr) + if err0 != nil { + continue + } + unique[info.ID] = info + } + for _, addr := range backup { + info, err0 := peer.AddrInfoFromP2pAddr(addr) + if err0 != nil { + continue + } + unique[info.ID] = info + } + var result []peer.AddrInfo + for _, addr := range unique { + result = append(result, *addr) + } + return result +} + +func mergeP2PAddrInfoResolvedAddresses(primary, backup []peer.AddrInfo) []peer.AddrInfo { + // deduplicate addresses by PeerID + unique := make(map[peer.ID]peer.AddrInfo) + for _, addr := range primary { + unique[addr.ID] = addr + } + for _, addr := range backup { + unique[addr.ID] = addr + } + var result []peer.AddrInfo + for _, addr := range unique { + result = append(result, addr) + } + return result } type p2pPeerStats struct { txReceived atomic.Uint64 } +// gossipSubPeer implements the DeadlineSettableConn, IPAddressable, and ErlClient interfaces. +type gossipSubPeer struct { + peerID peer.ID + net GossipNode + routingAddr [8]byte +} + +func (p gossipSubPeer) GetNetwork() GossipNode { return p.net } + +func (p gossipSubPeer) OnClose(f func()) { + net := p.GetNetwork().(*P2PNetwork) + net.wsPeersLock.Lock() + defer net.wsPeersLock.Unlock() + if wsp, ok := net.wsPeers[p.peerID]; ok { + wsp.OnClose(f) + } +} + +func (p gossipSubPeer) RoutingAddr() []byte { + return p.routingAddr[:] +} + // NewP2PNetwork returns an instance of GossipNode that uses the p2p.Service -func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID) (*P2PNetwork, error) { +func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, node NodeInfo) (*P2PNetwork, error) { const readBufferLen = 2048 // create Peerstore and add phonebook addresses @@ -77,20 +223,31 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo for malAddr, malErr := range malformedAddrs { log.Infof("Ignoring malformed phonebook address %s: %s", malAddr, malErr) } - pstore, err := peerstore.NewPeerStore(addrInfo) + pstore, err := peerstore.NewPeerStore(addrInfo, string(networkID)) if err != nil { return nil, err } + relayMessages := cfg.IsGossipServer() || cfg.ForceRelayMessages net := &P2PNetwork{ - log: log, - config: cfg, - genesisID: genesisID, - networkID: networkID, - topicTags: map[protocol.Tag]string{"TX": p2p.TXTopicName}, - wsPeers: make(map[peer.ID]*wsPeer), - peerStats: make(map[peer.ID]*p2pPeerStats), + log: log, + config: cfg, + genesisID: genesisID, + networkID: networkID, + topicTags: map[protocol.Tag]string{protocol.TxnTag: p2p.TXTopicName}, + wsPeers: make(map[peer.ID]*wsPeer), + wsPeersToIDs: make(map[*wsPeer]peer.ID), + peerStats: make(map[peer.ID]*p2pPeerStats), + nodeInfo: node, + pstore: pstore, + relayMessages: relayMessages, + peerStater: peerConnectionStater{ + log: log, + peerConnectionsUpdateInterval: time.Duration(cfg.PeerConnectionsUpdateInterval) * time.Second, + lastPeerConnectionsSent: time.Now(), + }, } + net.ctx, net.ctxCancel = context.WithCancel(context.Background()) net.handler = msgHandler{ ctx: net.ctx, @@ -106,10 +263,39 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo broadcastQueueBulk: make(chan broadcastRequest, 100), } - net.service, err = p2p.MakeService(net.ctx, log, cfg, datadir, pstore, net.wsStreamHandler) + p2p.EnableP2PLogging(log, logging.Level(cfg.BaseLoggerDebugLevel)) + + h, la, err := p2p.MakeHost(cfg, datadir, pstore) if err != nil { return nil, err } + log.Infof("P2P host created: peer ID %s addrs %s", h.ID(), h.Addrs()) + + net.service, err = p2p.MakeService(net.ctx, log, cfg, h, la, net.wsStreamHandler, addrInfo) + if err != nil { + return nil, err + } + + bootstrapper := &bootstrapper{ + cfg: cfg, + networkID: networkID, + phonebookPeers: addrInfo, + resolveController: dnsaddr.NewMultiaddrDNSResolveController(cfg.DNSSecurityTXTEnforced(), ""), + log: net.log, + } + net.bootstrapperStart = bootstrapper.start + net.bootstrapperStop = bootstrapper.stop + + if cfg.EnableDHTProviders { + disc, err0 := p2p.MakeCapabilitiesDiscovery(net.ctx, cfg, h, networkID, net.log, bootstrapper.BootstrapFunc) + if err0 != nil { + log.Errorf("Failed to create dht node capabilities discovery: %v", err) + return nil, err + } + net.capabilitiesDiscovery = disc + } + + net.httpServer = p2p.MakeHTTPServer(h) err = net.setup() if err != nil { @@ -126,10 +312,30 @@ func (n *P2PNetwork) setup() error { return nil } +// PeerID returns this node's peer ID. +func (n *P2PNetwork) PeerID() p2p.PeerID { + return p2p.PeerID(n.service.ID()) +} + +// PeerIDSigner returns an identityChallengeSigner that uses the libp2p peer ID's private key. +func (n *P2PNetwork) PeerIDSigner() identityChallengeSigner { + return n.service.IDSigner() +} + // Start threads, listen on sockets. -func (n *P2PNetwork) Start() { - n.wg.Add(1) - go n.txTopicHandleLoop() +func (n *P2PNetwork) Start() error { + n.bootstrapperStart() + err := n.service.Start() + if err != nil { + return err + } + + wantTXGossip := n.relayMessages || n.config.ForceFetchTransactions || n.nodeInfo.IsParticipating() + if wantTXGossip { + n.wantTXGossip.Store(true) + n.wg.Add(1) + go n.txTopicHandleLoop() + } if n.wsPeersConnectivityCheckTicker != nil { n.wsPeersConnectivityCheckTicker.Stop() @@ -141,16 +347,31 @@ func (n *P2PNetwork) Start() { go n.handler.messageHandlerThread(&n.wg, n.wsPeersConnectivityCheckTicker.C, n, "network", "P2PNetwork") } + n.wg.Add(1) + go n.httpdThread() + n.wg.Add(1) go n.broadcaster.broadcastThread(&n.wg, n, "network", "P2PNetwork") - n.service.DialPeersUntilTargetCount(n.config.GossipFanout) n.wg.Add(1) go n.meshThread() + + if n.capabilitiesDiscovery != nil { + n.capabilitiesDiscovery.AdvertiseCapabilities(n.nodeInfo.Capabilities()...) + } + + return nil } // Stop closes sockets and stop threads. func (n *P2PNetwork) Stop() { + if n.capabilitiesDiscovery != nil { + err := n.capabilitiesDiscovery.Close() + if err != nil { + n.log.Warnf("Error closing capabilities discovery: %v", err) + } + } + n.handler.ClearHandlers([]Tag{}) if n.wsPeersConnectivityCheckTicker != nil { n.wsPeersConnectivityCheckTicker.Stop() @@ -159,6 +380,8 @@ func (n *P2PNetwork) Stop() { n.innerStop() n.ctxCancel() n.service.Close() + n.bootstrapperStop() + n.httpServer.Close() n.wg.Wait() } @@ -176,22 +399,83 @@ func (n *P2PNetwork) innerStop() { n.log.Warnf("Error closing peer %s: %v", peerID, err) } delete(n.wsPeers, peerID) + delete(n.wsPeersToIDs, peer) } n.wsPeersLock.Unlock() closeGroup.Wait() } +// meshThreadInner fetches nodes from DHT and attempts to connect to them +func (n *P2PNetwork) meshThreadInner() int { + defer n.service.DialPeersUntilTargetCount(n.config.GossipFanout) + + // fetch peers from DNS + var dnsPeers, dhtPeers []peer.AddrInfo + dnsPeers = dnsLookupBootstrapPeers(n.log, n.config, n.networkID, dnsaddr.NewMultiaddrDNSResolveController(n.config.DNSSecurityTXTEnforced(), "")) + + // discover peers from DHT + if n.capabilitiesDiscovery != nil { + var err error + dhtPeers, err = n.capabilitiesDiscovery.PeersForCapability(p2p.Gossip, n.config.GossipFanout) + if err != nil { + n.log.Warnf("Error getting relay nodes from capabilities discovery: %v", err) + } + n.log.Debugf("Discovered %d gossip peers from DHT", len(dhtPeers)) + } + + peers := mergeP2PAddrInfoResolvedAddresses(dnsPeers, dhtPeers) + replace := make([]interface{}, 0, len(peers)) + for i := range peers { + replace = append(replace, &peers[i]) + } + if len(peers) > 0 { + n.pstore.ReplacePeerList(replace, string(n.networkID), phonebook.PhoneBookEntryRelayRole) + } + return len(peers) +} + func (n *P2PNetwork) meshThread() { defer n.wg.Done() - timer := time.NewTicker(meshThreadInterval) + + timer := time.NewTicker(1) // start immediately and reset after + + // Add exponential backoff with jitter to the mesh thread to handle new networks startup + // when no DNS or DHT peers are available. + // The parameters produce approximate the following delays (although they are random but the sequence give the idea): + // 2 2.4 4.6 9 20 19.5 28 24 14 14 35 60 60 + ebf := backoff.NewExponentialDecorrelatedJitter(2*time.Second, meshThreadInterval, 3.0, rand.NewSource(rand.Int63())) + eb := ebf() + defer timer.Stop() for { select { case <-timer.C: - n.service.DialPeersUntilTargetCount(n.config.GossipFanout) + numPeers := n.meshThreadInner() + if numPeers > 0 { + // found something, reset timer to the default value + timer.Reset(meshThreadInterval) + eb.Reset() + } else { + // no peers found, backoff + timer.Reset(eb.Delay()) + } case <-n.ctx.Done(): return } + + // send the currently connected peers information to the + // telemetry server; that would allow the telemetry server + // to construct a cross-node map of all the nodes interconnections. + n.peerStater.sendPeerConnectionsTelemetryStatus(n) + } +} + +func (n *P2PNetwork) httpdThread() { + defer n.wg.Done() + err := n.httpServer.Serve() + if err != nil { + n.log.Errorf("Error serving libp2phttp: %v", err) + return } } @@ -240,31 +524,42 @@ func (n *P2PNetwork) Broadcast(ctx context.Context, tag protocol.Tag, data []byt // Relay message func (n *P2PNetwork) Relay(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error { - return n.Broadcast(ctx, tag, data, wait, except) + if n.relayMessages { + return n.Broadcast(ctx, tag, data, wait, except) + } + return nil } // Disconnect from a peer, probably due to protocol errors. -func (n *P2PNetwork) Disconnect(badnode Peer) { - node, ok := badnode.(peer.ID) - if !ok { - n.log.Warnf("Unknown peer type %T", badnode) - return - } +func (n *P2PNetwork) Disconnect(badpeer DisconnectablePeer) { + var peerID peer.ID + var wsp *wsPeer + n.wsPeersLock.Lock() defer n.wsPeersLock.Unlock() - if wsPeer, ok := n.wsPeers[node]; ok { - wsPeer.CloseAndWait(time.Now().Add(peerDisconnectionAckDuration)) - delete(n.wsPeers, node) + switch p := badpeer.(type) { + case gossipSubPeer: // Disconnect came from a message received via GossipSub + peerID, wsp = p.peerID, n.wsPeers[p.peerID] + case *wsPeer: // Disconnect came from a message received via wsPeer + peerID, wsp = n.wsPeersToIDs[p], p + default: + n.log.Warnf("Unknown peer type %T", badpeer) + return + } + if wsp != nil { + wsp.CloseAndWait(time.Now().Add(peerDisconnectionAckDuration)) + delete(n.wsPeers, peerID) + delete(n.wsPeersToIDs, wsp) } else { - n.log.Warnf("Could not find wsPeer reference for peer %s", node) + n.log.Warnf("Could not find wsPeer reference for peer %s", peerID) } - err := n.service.ClosePeer(node) + err := n.service.ClosePeer(peerID) if err != nil { - n.log.Warnf("Error disconnecting from peer %s: %v", node, err) + n.log.Warnf("Error disconnecting from peer %s: %v", peerID, err) } } -func (n *P2PNetwork) disconnectThread(badnode Peer, reason disconnectReason) { +func (n *P2PNetwork) disconnectThread(badnode DisconnectablePeer, reason disconnectReason) { defer n.wg.Done() n.Disconnect(badnode) // ignores reason } @@ -278,23 +573,106 @@ func (n *P2PNetwork) DisconnectPeers() { // RegisterHTTPHandler path accepts gorilla/mux path annotations func (n *P2PNetwork) RegisterHTTPHandler(path string, handler http.Handler) { + n.httpServer.RegisterHTTPHandler(path, handler) } // RequestConnectOutgoing asks the system to actually connect to peers. // `replace` optionally drops existing connections before making new ones. // `quit` chan allows cancellation. func (n *P2PNetwork) RequestConnectOutgoing(replace bool, quit <-chan struct{}) { + n.meshThreadInner() +} + +func addrInfoToWsPeerCore(n *P2PNetwork, addrInfo *peer.AddrInfo) (wsPeerCore, bool) { + mas, err := peer.AddrInfoToP2pAddrs(addrInfo) + if err != nil { + n.log.Warnf("Archival AddrInfo conversion error: %v", err) + return wsPeerCore{}, false + } + if len(mas) == 0 { + n.log.Warnf("Archival AddrInfo: empty multiaddr for : %v", addrInfo) + return wsPeerCore{}, false + } + addr := mas[0].String() + + maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) + client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) + if err != nil { + n.log.Warnf("MakeHTTPClient failed: %v", err) + return wsPeerCore{}, false + } + + peerCore := makePeerCore( + n.ctx, n, n.log, n.handler.readBuffer, + addr, client, "", /*origin address*/ + ) + return peerCore, true } // GetPeers returns a list of Peers we could potentially send a direct message to. func (n *P2PNetwork) GetPeers(options ...PeerOption) []Peer { - // currently returns same list of peers for all PeerOption filters. peers := make([]Peer, 0) - n.wsPeersLock.RLock() - for _, peer := range n.wsPeers { - peers = append(peers, Peer(peer)) + for _, option := range options { + switch option { + case PeersConnectedOut: + n.wsPeersLock.RLock() + for _, peer := range n.wsPeers { + if peer.outgoing { + peers = append(peers, Peer(peer)) + } + } + n.wsPeersLock.RUnlock() + case PeersPhonebookRelays: + const maxNodes = 100 + peerIDs := n.pstore.GetAddresses(maxNodes, phonebook.PhoneBookEntryRelayRole) + for _, peerInfo := range peerIDs { + peerInfo := peerInfo.(*peer.AddrInfo) + if peerCore, ok := addrInfoToWsPeerCore(n, peerInfo); ok { + peers = append(peers, &peerCore) + } + } + if n.log.GetLevel() >= logging.Debug && len(peers) > 0 { + addrs := make([]string, 0, len(peers)) + for _, peer := range peers { + addrs = append(addrs, peer.(*wsPeerCore).GetAddress()) + } + n.log.Debugf("Relay node(s) from peerstore: %v", addrs) + } + case PeersPhonebookArchivalNodes: + // query known archival nodes from DHT if enabled + if n.config.EnableDHTProviders { + const nodesToFind = 5 + infos, err := n.capabilitiesDiscovery.PeersForCapability(p2p.Archival, nodesToFind) + if err != nil { + n.log.Warnf("Error getting archival nodes from capabilities discovery: %v", err) + return peers + } + n.log.Debugf("Got %d archival node(s) from DHT", len(infos)) + for _, addrInfo := range infos { + // TODO: remove after go1.22 + info := addrInfo + if peerCore, ok := addrInfoToWsPeerCore(n, &info); ok { + peers = append(peers, &peerCore) + } + } + if n.log.GetLevel() >= logging.Debug && len(peers) > 0 { + addrs := make([]string, 0, len(peers)) + for _, peer := range peers { + addrs = append(addrs, peer.(*wsPeerCore).GetAddress()) + } + n.log.Debugf("Archival node(s) from DHT: %v", addrs) + } + } + case PeersConnectedIn: + n.wsPeersLock.RLock() + for _, peer := range n.wsPeers { + if !peer.outgoing { + peers = append(peers, Peer(peer)) + } + } + n.wsPeersLock.RUnlock() + } } - n.wsPeersLock.RUnlock() return peers } @@ -308,31 +686,67 @@ func (n *P2PNetwork) ClearHandlers() { n.handler.ClearHandlers([]Tag{}) } -// GetRoundTripper returns a Transport that would limit the number of outgoing connections. -func (n *P2PNetwork) GetRoundTripper() http.RoundTripper { - return http.DefaultTransport +// RegisterProcessors adds to the set of given message handlers. +func (n *P2PNetwork) RegisterProcessors(dispatch []TaggedMessageProcessor) { + n.handler.RegisterProcessors(dispatch) +} + +// ClearProcessors deregisters all the existing message handlers. +func (n *P2PNetwork) ClearProcessors() { + n.handler.ClearProcessors([]Tag{}) +} + +// GetHTTPClient returns a http.Client with a suitable for the network Transport +// that would also limit the number of outgoing connections. +func (n *P2PNetwork) GetHTTPClient(address string) (*http.Client, error) { + addrInfo, err := peer.AddrInfoFromString(address) + if err != nil { + return nil, err + } + maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) + return p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) } // OnNetworkAdvance notifies the network library that the agreement protocol was able to make a notable progress. // this is the only indication that we have that we haven't formed a clique, where all incoming messages // arrive very quickly, but might be missing some votes. The usage of this call is expected to have similar // characteristics as with a watchdog timer. -func (n *P2PNetwork) OnNetworkAdvance() {} +func (n *P2PNetwork) OnNetworkAdvance() { + if n.nodeInfo != nil { + old := n.wantTXGossip.Load() + new := n.relayMessages || n.config.ForceFetchTransactions || n.nodeInfo.IsParticipating() + if old != new { + n.wantTXGossip.Store(new) + if new { + n.wg.Add(1) + go n.txTopicHandleLoop() + } + } + } +} // GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same // request that was provided to the http handler ( or provide a fallback Context() to that ) -func (n *P2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { return nil } - -// SubstituteGenesisID substitutes the "{genesisID}" with their network-specific genesisID. -func (n *P2PNetwork) SubstituteGenesisID(rawURL string) string { - return strings.Replace(rawURL, "{genesisID}", n.genesisID, -1) +func (n *P2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) { + addr := request.Context().Value(http.LocalAddrContextKey).(net.Addr) + peerID, err := peer.Decode(addr.String()) + if err != nil { + n.log.Infof("GetHTTPRequestConnection failed to decode %s", addr.String()) + return nil + } + conn, ok := n.service.GetStream(peerID) + if !ok { + n.log.Warnf("GetHTTPRequestConnection no such stream for peer %s", peerID.String()) + return nil + } + return conn } // wsStreamHandler is a callback that the p2p package calls when a new peer connects and establishes a // stream for the websocket protocol. -func (n *P2PNetwork) wsStreamHandler(ctx context.Context, peer peer.ID, stream network.Stream, incoming bool) { +func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) { if stream.Protocol() != p2p.AlgorandWsProtocol { - n.log.Warnf("unknown protocol %s", stream.Protocol()) + n.log.Warnf("unknown protocol %s from peer%s", stream.Protocol(), p2pPeer) return } @@ -340,7 +754,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, peer peer.ID, stream n var initMsg [1]byte rn, err := stream.Read(initMsg[:]) if rn == 0 || err != nil { - n.log.Warnf("wsStreamHandler: error reading initial message: %s", err) + n.log.Warnf("wsStreamHandler: error reading initial message: %s, peer %s (%s)", err, p2pPeer, stream.Conn().RemoteMultiaddr().String()) return } } else { @@ -352,21 +766,63 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, peer peer.ID, stream n } // get address for peer ID - addr := stream.Conn().RemoteMultiaddr().String() + ma := stream.Conn().RemoteMultiaddr() + addr := ma.String() if addr == "" { - n.log.Warnf("Could not get address for peer %s", peer) + n.log.Warnf("Could not get address for peer %s", p2pPeer) } // create a wsPeer for this stream and added it to the peers map. + + addrInfo := &peer.AddrInfo{ID: p2pPeer, Addrs: []multiaddr.Multiaddr{ma}} + maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) + client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) + if err != nil { + client = nil + } + peerCore := makePeerCore(ctx, n, n.log, n.handler.readBuffer, addr, client, addr) wsp := &wsPeer{ - wsPeerCore: makePeerCore(ctx, n, n.log, n.handler.readBuffer, addr, n.GetRoundTripper(), addr), + wsPeerCore: peerCore, conn: &wsPeerConnP2PImpl{stream: stream}, outgoing: !incoming, } + protos, err := n.pstore.GetProtocols(p2pPeer) + if err != nil { + n.log.Warnf("Error getting protocols for peer %s: %v", p2pPeer, err) + } + wsp.TelemetryGUID, wsp.InstanceName = p2p.GetPeerTelemetryInfo(protos) + wsp.init(n.config, outgoingMessagesBufferSize) n.wsPeersLock.Lock() - n.wsPeers[peer] = wsp + n.wsPeers[p2pPeer] = wsp + n.wsPeersToIDs[wsp] = p2pPeer n.wsPeersLock.Unlock() n.wsPeersChangeCounter.Add(1) + + event := "ConnectedOut" + msg := "Made outgoing connection to peer %s" + if incoming { + event = "ConnectedIn" + msg = "Accepted incoming connection from peer %s" + } + localAddr, has := n.Address() + if !has { + n.log.Warn("Could not get local address") + } + n.log.With("event", event).With("remote", addr).With("local", localAddr).Infof(msg, p2pPeer.String()) + + if n.log.GetLevel() >= logging.Debug { + n.log.Debugf("streams for %s conn %s ", stream.Conn().Stat().Direction.String(), stream.Conn().ID()) + for _, s := range stream.Conn().GetStreams() { + n.log.Debugf("%s stream %s protocol %s", s.Stat().Direction.String(), s.ID(), s.Protocol()) + } + } + n.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerEvent, + telemetryspec.PeerEventDetails{ + Address: addr, + TelemetryGUID: wsp.TelemetryGUID, + Incoming: incoming, + InstanceName: wsp.InstanceName, + }) } // peerRemoteClose called from wsPeer to report that it has closed @@ -374,8 +830,30 @@ func (n *P2PNetwork) peerRemoteClose(peer *wsPeer, reason disconnectReason) { remotePeerID := peer.conn.(*wsPeerConnP2PImpl).stream.Conn().RemotePeer() n.wsPeersLock.Lock() delete(n.wsPeers, remotePeerID) + delete(n.wsPeersToIDs, peer) n.wsPeersLock.Unlock() n.wsPeersChangeCounter.Add(1) + + eventDetails := telemetryspec.PeerEventDetails{ + Address: peer.GetAddress(), // p2p peers store p2p addresses + TelemetryGUID: peer.TelemetryGUID, + InstanceName: peer.InstanceName, + Incoming: !peer.outgoing, + } + if peer.outgoing { + eventDetails.Endpoint = peer.GetAddress() + eventDetails.MessageDelay = peer.peerMessageDelay + } + + n.log.EventWithDetails(telemetryspec.Network, telemetryspec.DisconnectPeerEvent, + telemetryspec.DisconnectPeerEventDetails{ + PeerEventDetails: eventDetails, + Reason: string(reason), + TXCount: peer.txMessageCount.Load(), + MICount: peer.miMessageCount.Load(), + AVCount: peer.avMessageCount.Load(), + PPCount: peer.ppMessageCount.Load(), + }) } func (n *P2PNetwork) peerSnapshot(dest []*wsPeer) ([]*wsPeer, int32) { @@ -417,6 +895,7 @@ func (n *P2PNetwork) txTopicHandleLoop() { n.log.Errorf("Failed to subscribe to topic %s: %v", p2p.TXTopicName, err) return } + n.log.Debugf("Subscribed to topic %s", p2p.TXTopicName) for { msg, err := sub.Next(n.ctx) @@ -424,21 +903,40 @@ func (n *P2PNetwork) txTopicHandleLoop() { if err != pubsub.ErrSubscriptionCancelled && err != context.Canceled { n.log.Errorf("Error reading from subscription %v, peerId %s", err, n.service.ID()) } + n.log.Debugf("Cancelling subscription to topic %s due Subscription.Next error: %v", p2p.TXTopicName, err) sub.Cancel() return } + // if there is a self-sent the message no need to process it. + if msg.ReceivedFrom == n.service.ID() { + continue + } + + _ = n.handler.Process(msg.ValidatorData.(ValidatedMessage)) - // discard TX message. - // from gossipsub's point of view, it's just waiting to hear back from the validator, - // and txHandler does all its work in the validator, so we don't need to do anything here - _ = msg + // participation or configuration change, cancel subscription and quit + if !n.wantTXGossip.Load() { + n.log.Debugf("Cancelling subscription to topic %s due participation change", p2p.TXTopicName) + sub.Cancel() + return + } } } // txTopicValidator calls txHandler to validate and process incoming transactions. func (n *P2PNetwork) txTopicValidator(ctx context.Context, peerID peer.ID, msg *pubsub.Message) pubsub.ValidationResult { + var routingAddr [8]byte + n.wsPeersLock.Lock() + if wsp, ok := n.wsPeers[peerID]; ok { + copy(routingAddr[:], wsp.RoutingAddr()) + } else { + // well, otherwise use last 8 bytes of peerID + copy(routingAddr[:], peerID[len(peerID)-8:]) + } + n.wsPeersLock.Unlock() + inmsg := IncomingMessage{ - Sender: msg.ReceivedFrom, + Sender: gossipSubPeer{peerID: msg.ReceivedFrom, net: n, routingAddr: routingAddr}, Tag: protocol.TxnTag, Data: msg.Data, Net: n, @@ -446,7 +944,7 @@ func (n *P2PNetwork) txTopicValidator(ctx context.Context, peerID peer.ID, msg * } // if we sent the message, don't validate it - if inmsg.Sender == n.service.ID() { + if msg.ReceivedFrom == n.service.ID() { return pubsub.ValidationAccept } @@ -459,14 +957,15 @@ func (n *P2PNetwork) txTopicValidator(ctx context.Context, peerID peer.ID, msg * peerStats.txReceived.Add(1) n.peerStatsMu.Unlock() - outmsg := n.handler.Handle(inmsg) + outmsg := n.handler.Validate(inmsg) // there was a decision made in the handler about this message switch outmsg.Action { case Ignore: return pubsub.ValidationIgnore case Disconnect: return pubsub.ValidationReject - case Broadcast: // TxHandler.processIncomingTxn does not currently return this Action + case Accept: + msg.ValidatorData = outmsg return pubsub.ValidationAccept default: n.log.Warnf("handler returned invalid action %d", outmsg.Action) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index c67ef5b1fc..5bd582ead0 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -18,49 +18,64 @@ package network import ( "context" + "errors" "fmt" + "io" + "net/http" + "sync" "sync/atomic" "testing" "time" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/limitcaller" "github.com/algorand/go-algorand/network/p2p" + "github.com/algorand/go-algorand/network/p2p/dnsaddr" + "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util" pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" - peerstore "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" ) +func (n *P2PNetwork) hasPeers() bool { + n.wsPeersLock.RLock() + defer n.wsPeersLock.RUnlock() + return len(n.wsPeers) > 0 +} + func TestP2PSubmitTX(t *testing.T) { partitiontest.PartitionTest(t) cfg := config.GetDefaultLocal() + cfg.ForceFetchTransactions = true log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) - peerInfoA := netA.service.AddrInfo() + netA.Start() + defer netA.Stop() - addrsA, err := peerstore.AddrInfoToP2pAddrs(&peerInfoA) + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) require.NoError(t, err) require.NotZero(t, addrsA[0]) - netA.Start() - defer netA.Stop() multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} - netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet) + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) netB.Start() defer netB.Stop() - netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet) - + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) netC.Start() defer netC.Stop() @@ -75,19 +90,36 @@ func TestP2PSubmitTX(t *testing.T) { 2*time.Second, 50*time.Millisecond, ) + require.Eventually(t, func() bool { + return netA.hasPeers() && netB.hasPeers() && netC.hasPeers() + }, 2*time.Second, 50*time.Millisecond) + + // for some reason the above check is not enough in race builds on CI time.Sleep(time.Second) // give time for peers to connect. + // now we should be connected in a line: B <-> A <-> C where both B and C are connected to A but not each other // Since we aren't using the transaction handler in this test, we need to register a pass-through handler - passThroughHandler := []TaggedMessageHandler{ - {Tag: protocol.TxnTag, MessageHandler: HandlerFunc(func(msg IncomingMessage) OutgoingMessage { - return OutgoingMessage{Action: Broadcast} - })}, + passThroughHandler := []TaggedMessageProcessor{ + { + Tag: protocol.TxnTag, + MessageHandler: struct { + ProcessorValidateFunc + ProcessorHandleFunc + }{ + ProcessorValidateFunc(func(msg IncomingMessage) ValidatedMessage { + return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: nil} + }), + ProcessorHandleFunc(func(msg ValidatedMessage) OutgoingMessage { + return OutgoingMessage{Action: Ignore} + }), + }, + }, } - netA.RegisterHandlers(passThroughHandler) - netB.RegisterHandlers(passThroughHandler) - netC.RegisterHandlers(passThroughHandler) + netA.RegisterProcessors(passThroughHandler) + netB.RegisterProcessors(passThroughHandler) + netC.RegisterProcessors(passThroughHandler) // send messages from B and confirm that they get received by C (via A) for i := 0; i < 10; i++ { @@ -111,45 +143,139 @@ func TestP2PSubmitTX(t *testing.T) { ) } -func TestP2PSubmitWS(t *testing.T) { +// TestP2PSubmitTXNoGossip tests nodes without gossip enabled cannot receive transactions +func TestP2PSubmitTXNoGossip(t *testing.T) { partitiontest.PartitionTest(t) cfg := config.GetDefaultLocal() + cfg.ForceFetchTransactions = true log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) + netA.Start() + defer netA.Stop() peerInfoA := netA.service.AddrInfo() - addrsA, err := peerstore.AddrInfoToP2pAddrs(&peerInfoA) + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) require.NoError(t, err) require.NotZero(t, addrsA[0]) - netA.Start() - defer netA.Stop() multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} - netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet) + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) netB.Start() defer netB.Stop() - netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet) + require.Eventually( + t, + func() bool { + return len(netA.service.ListPeersForTopic(p2p.TXTopicName)) == 1 && + len(netB.service.ListPeersForTopic(p2p.TXTopicName)) == 1 + }, + 2*time.Second, + 50*time.Millisecond, + ) + // run netC in NPN mode (no relay => no gossip sup => no TX receiving) + cfg.ForceFetchTransactions = false + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) netC.Start() defer netC.Stop() + require.Eventually(t, func() bool { + return netA.hasPeers() && netB.hasPeers() && netC.hasPeers() + }, 2*time.Second, 50*time.Millisecond) + + time.Sleep(time.Second) // give time for peers to connect. + + // ensure netC cannot receive messages + + passThroughHandler := []TaggedMessageProcessor{ + { + Tag: protocol.TxnTag, + MessageHandler: struct { + ProcessorValidateFunc + ProcessorHandleFunc + }{ + ProcessorValidateFunc(func(msg IncomingMessage) ValidatedMessage { + return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: nil} + }), + ProcessorHandleFunc(func(msg ValidatedMessage) OutgoingMessage { + return OutgoingMessage{Action: Ignore} + }), + }, + }, + } + + netB.RegisterProcessors(passThroughHandler) + netC.RegisterProcessors(passThroughHandler) + for i := 0; i < 10; i++ { + err = netA.Broadcast(context.Background(), protocol.TxnTag, []byte(fmt.Sprintf("test %d", i)), false, nil) + require.NoError(t, err) + } + + // check netB received the messages require.Eventually( t, func() bool { - return len(netA.service.ListPeersForTopic(p2p.TXTopicName)) == 2 && - len(netB.service.ListPeersForTopic(p2p.TXTopicName)) == 1 && - len(netC.service.ListPeersForTopic(p2p.TXTopicName)) == 1 + netB.peerStatsMu.Lock() + netBpeerStatsA, ok := netB.peerStats[netA.service.ID()] + netB.peerStatsMu.Unlock() + if !ok { + return false + } + return netBpeerStatsA.txReceived.Load() == 10 }, - 2*time.Second, + 1*time.Second, 50*time.Millisecond, ) - time.Sleep(time.Second) // XX give time for peers to connect. Knowing about them being subscribed to topics is clearly not enough + + // check netC did not receive the messages + netC.peerStatsMu.Lock() + _, ok := netC.peerStats[netA.service.ID()] + netC.peerStatsMu.Unlock() + require.False(t, ok) +} + +func TestP2PSubmitWS(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + log := logging.TestingLog(t) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + require.NoError(t, err) + + err = netA.Start() + require.NoError(t, err) + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsA[0]) + + multiAddrStr := addrsA[0].String() + phoneBookAddresses := []string{multiAddrStr} + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + require.NoError(t, err) + err = netB.Start() + require.NoError(t, err) + defer netB.Stop() + + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + require.NoError(t, err) + err = netC.Start() + require.NoError(t, err) + defer netC.Stop() + + require.Eventually(t, func() bool { + return netA.hasPeers() && netB.hasPeers() && netC.hasPeers() + }, 2*time.Second, 50*time.Millisecond) + + time.Sleep(time.Second) // give time for peers to connect. + // now we should be connected in a line: B <-> A <-> C where both B and C are connected to A but not each other testTag := protocol.AgreementVoteTag @@ -189,6 +315,10 @@ type mockService struct { peers map[peer.ID]peer.AddrInfo } +func (s *mockService) Start() error { + return nil +} + func (s *mockService) Close() error { return nil } @@ -197,6 +327,10 @@ func (s *mockService) ID() peer.ID { return s.id } +func (s *mockService) IDSigner() *p2p.PeerIDChallengeSigner { + panic("not implemented") +} + func (s *mockService) AddrInfo() peer.AddrInfo { return peer.AddrInfo{ ID: s.id, @@ -213,9 +347,7 @@ func (s *mockService) DialPeersUntilTargetCount(targetConnCount int) { } func (s *mockService) ClosePeer(peer peer.ID) error { - if _, ok := s.peers[peer]; ok { - delete(s.peers, peer) - } + delete(s.peers, peer) return nil } @@ -227,15 +359,15 @@ func (s *mockService) ListPeersForTopic(topic string) []peer.ID { return nil } -func (s *mockService) Subscribe(topic string, val pubsub.ValidatorEx) (*pubsub.Subscription, error) { +func (s *mockService) Subscribe(topic string, val pubsub.ValidatorEx) (p2p.SubNextCancellable, error) { return nil, nil } func (s *mockService) Publish(ctx context.Context, topic string, data []byte) error { return nil } -func (s *mockService) setAddrs(addrs []ma.Multiaddr) { - s.addrs = addrs +func (s *mockService) GetStream(peer.ID) (network.Stream, bool) { + return nil, false } func makeMockService(id peer.ID, addrs []ma.Multiaddr) *mockService { @@ -250,7 +382,7 @@ func TestP2PNetworkAddress(t *testing.T) { cfg := config.GetDefaultLocal() log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) defer netA.Stop() require.NoError(t, err) addrInfo := netA.service.AddrInfo() @@ -304,3 +436,692 @@ func TestP2PNetworkAddress(t *testing.T) { require.False(t, ok) require.Empty(t, retAddr) } + +type nilResolveController struct{} + +func (c *nilResolveController) Resolver() dnsaddr.Resolver { + return nil +} + +func (c *nilResolveController) NextResolver() dnsaddr.Resolver { + return nil +} + +type mockResolveController struct { + nilResolveController +} + +func (c *mockResolveController) Resolver() dnsaddr.Resolver { + return &mockResolver{} +} + +type mockResolver struct{} + +func (r *mockResolver) Resolve(ctx context.Context, _ ma.Multiaddr) ([]ma.Multiaddr, error) { + // return random stuff each time + _, publicKey, err := crypto.GenerateKeyPair(crypto.RSA, 2048) + if err != nil { + panic(err) + } + peerID, err := peer.IDFromPublicKey(publicKey) + if err != nil { + panic(err) + } + maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/p2p/" + peerID.String()) + return []ma.Multiaddr{maddr}, err +} + +func TestP2PBootstrapFunc(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + + b := bootstrapper{} + require.Nil(t, b.BootstrapFunc()) + + b.started.Store(true) + p := peer.AddrInfo{ID: "test"} + b.phonebookPeers = []*peer.AddrInfo{&p} + require.Equal(t, []peer.AddrInfo{p}, b.BootstrapFunc()) + + b.phonebookPeers = nil + + b.cfg = config.GetDefaultLocal() + b.cfg.DNSBootstrapID = ".algodev.network" + b.cfg.DNSSecurityFlags = 0 + b.networkID = "devnet" + b.resolveController = &mockResolveController{} + + addrs := b.BootstrapFunc() + + require.GreaterOrEqual(t, len(addrs), 1) + addr := addrs[0] + require.Equal(t, len(addr.Addrs), 1) + require.GreaterOrEqual(t, len(addr.Addrs), 1) +} + +func TestP2PdnsLookupBootstrapPeersFailure(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + cfg.DNSSecurityFlags = 0 + cfg.DNSBootstrapID = "non-existent.algodev.network" + + controller := nilResolveController{} + addrs := dnsLookupBootstrapPeers(logging.TestingLog(t), cfg, "test", &controller) + + require.Equal(t, 0, len(addrs)) +} + +func TestP2PdnsLookupBootstrapPeersInvalidAddr(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + cfg.DNSSecurityFlags = 0 + cfg.DNSBootstrapID = ".algodev.network" + + controller := nilResolveController{} + addrs := dnsLookupBootstrapPeers(logging.TestingLog(t), cfg, "testInvalidAddr", &controller) + + require.Equal(t, 0, len(addrs)) +} + +func TestP2PdnsLookupBootstrapPeersWithBackup(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + cfg.DNSSecurityFlags = 0 + cfg.DNSBootstrapID = ".algodev.network" + + controller := &mockResolveController{} + addrs := dnsLookupBootstrapPeers(logging.TestingLog(t), cfg, "test", controller) + require.GreaterOrEqual(t, len(addrs), 1) + + cfg.DNSBootstrapID = ".algodev.network?backup=.backup.algodev.network" + addrs = dnsLookupBootstrapPeers(logging.TestingLog(t), cfg, "test", controller) + require.GreaterOrEqual(t, len(addrs), 2) + +} + +type capNodeInfo struct { + nopeNodeInfo + cap p2p.Capability +} + +func (ni *capNodeInfo) Capabilities() []p2p.Capability { + return []p2p.Capability{ni.cap} +} + +func waitForRouting(t *testing.T, disc *p2p.CapabilitiesDiscovery) { + refreshCtx, refCancel := context.WithTimeout(context.Background(), time.Second*5) + for { + select { + case <-refreshCtx.Done(): + refCancel() + require.Fail(t, "failed to populate routing table before timeout") + default: + if disc.RoutingTable().Size() > 0 { + refCancel() + return + } + } + time.Sleep(50 * time.Millisecond) + } +} + +// TestP2PNetworkDHTCapabilities runs nodes with capabilities and ensures that connected nodes +// can discover itself. The other nodes receive the first node in bootstrap list before starting. +// There is two variations of the test: only netA advertises capabilities, and all nodes advertise. +func TestP2PNetworkDHTCapabilities(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + cfg.EnableDHTProviders = true + log := logging.TestingLog(t) + + cap := p2p.Archival + tests := []struct { + name string + nis []NodeInfo + numCapPeers int + }{ + {"cap=all", []NodeInfo{&capNodeInfo{cap: cap}, &capNodeInfo{cap: cap}, &capNodeInfo{cap: cap}}, 2}, // each has 2 peers with capabilities + {"cap=netA", []NodeInfo{&capNodeInfo{cap: cap}, &nopeNodeInfo{}, &nopeNodeInfo{}}, 1}, // each has 1 peer with capabilities + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, test.nis[0]) + require.NoError(t, err) + + err = netA.Start() + require.NoError(t, err) + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsA[0]) + + multiAddrStr := addrsA[0].String() + phoneBookAddresses := []string{multiAddrStr} + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[1]) + require.NoError(t, err) + err = netB.Start() + require.NoError(t, err) + defer netB.Stop() + + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[2]) + require.NoError(t, err) + err = netC.Start() + require.NoError(t, err) + defer netC.Stop() + + require.Eventually(t, func() bool { + return netA.hasPeers() && netB.hasPeers() && netC.hasPeers() + }, 2*time.Second, 50*time.Millisecond) + + t.Logf("peers connected") + + nets := []*P2PNetwork{netA, netB, netC} + discs := []*p2p.CapabilitiesDiscovery{netA.capabilitiesDiscovery, netB.capabilitiesDiscovery, netC.capabilitiesDiscovery} + + var wg sync.WaitGroup + wg.Add(len(discs)) + for _, disc := range discs { + if disc == nil { + wg.Done() + continue + } + go func(disc *p2p.CapabilitiesDiscovery) { + defer wg.Done() + waitForRouting(t, disc) + }(disc) + } + wg.Wait() + + t.Logf("DHT is ready") + + // ensure all peers are connected - wait for connectivity as needed + for _, disc := range discs { + go func(disc *p2p.CapabilitiesDiscovery) { + require.Eventuallyf(t, func() bool { + return len(disc.Host().Network().Peers()) == 2 + }, time.Minute, time.Second, "Not all peers were found") + }(disc) + } + + wg.Add(len(discs)) + for i := range discs { + go func(idx int) { + disc := discs[idx] + defer wg.Done() + // skip netA since it is special for the test cap=netA + if test.name == "cap=netA" && disc == netA.capabilitiesDiscovery { + return + } + require.Eventuallyf(t, + func() bool { + peers, err := disc.PeersForCapability(cap, test.numCapPeers) + if err == nil && len(peers) == test.numCapPeers { + return true + } + return false + }, + time.Minute, + time.Second, + fmt.Sprintf("Not all expected %s cap peers were found", cap), + ) + // ensure GetPeers gets PeersPhonebookArchivalNodes peers + // it appears there are artifical peers because of listening on localhost and on a real network interface + // so filter out and save only unique peers by their IDs + net := nets[idx] + peers := net.GetPeers(PeersPhonebookArchivalNodes) + uniquePeerIDs := make(map[peer.ID]struct{}) + for _, p := range peers { + wsPeer := p.(*wsPeerCore) + pi, err := peer.AddrInfoFromString(wsPeer.GetAddress()) + require.NoError(t, err) + uniquePeerIDs[pi.ID] = struct{}{} + } + require.Equal(t, test.numCapPeers, len(uniquePeerIDs)) + }(i) + } + wg.Wait() + }) + } +} + +// TestMultiaddrConversionToFrom ensures Multiaddr can be serialized back to an address without losing information +func TestP2PMultiaddrConversionToFrom(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + const a = "/ip4/192.168.1.1/tcp/8180/p2p/Qmewz5ZHN1AAGTarRbMupNPbZRfg3p5jUGoJ3JYEatJVVk" + ma, err := ma.NewMultiaddr(a) + require.NoError(t, err) + require.Equal(t, a, ma.String()) + + // this conversion drops the p2p proto part + pi, err := peer.AddrInfoFromP2pAddr(ma) + require.NoError(t, err) + require.NotEqual(t, a, pi.Addrs[0].String()) + require.Len(t, pi.Addrs, 1) + + mas, err := peer.AddrInfoToP2pAddrs(pi) + require.NoError(t, err) + require.Len(t, mas, 1) + require.Equal(t, a, mas[0].String()) +} + +type p2phttpHandler struct { + tb testing.TB + retData string + net GossipNode +} + +func (h *p2phttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(h.retData)) + if r.URL.Path == "/check-conn" { + c := h.net.GetHTTPRequestConnection(r) + require.NotNil(h.tb, c) + } +} + +func TestP2PHTTPHandler(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.EnableDHTProviders = true + cfg.GossipFanout = 1 + log := logging.TestingLog(t) + + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + require.NoError(t, err) + + h := &p2phttpHandler{t, "hello", nil} + netA.RegisterHTTPHandler("/test", h) + + h2 := &p2phttpHandler{t, "world", netA} + netA.RegisterHTTPHandler("/check-conn", h2) + + netA.Start() + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsA[0]) + + httpClient, err := p2p.MakeHTTPClient(&peerInfoA) + require.NoError(t, err) + resp, err := httpClient.Get("/test") + require.NoError(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "hello", string(body)) + + // check another endpoint that also access the underlying connection/stream + httpClient, err = p2p.MakeHTTPClient(&peerInfoA) + require.NoError(t, err) + resp, err = httpClient.Get("/check-conn") + require.NoError(t, err) + defer resp.Body.Close() + + body, err = io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "world", string(body)) + + // check rate limiting client: + // zero clients allowed, rate limiting window (10s) is greater than queue deadline (1s) + pstore, err := peerstore.MakePhonebook(0, 10*time.Second) + require.NoError(t, err) + pstore.AddPersistentPeers([]interface{}{&peerInfoA}, "net", phonebook.PhoneBookEntryRelayRole) + httpClient, err = p2p.MakeHTTPClientWithRateLimit(&peerInfoA, pstore, 1*time.Second, 1) + require.NoError(t, err) + _, err = httpClient.Get("/test") + require.ErrorIs(t, err, limitcaller.ErrConnectionQueueingTimeout) +} + +func TestP2PRelay(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + cfg.ForceFetchTransactions = true + log := logging.TestingLog(t) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + require.NoError(t, err) + + err = netA.Start() + require.NoError(t, err) + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsA[0]) + + multiAddrStr := addrsA[0].String() + phoneBookAddresses := []string{multiAddrStr} + + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + require.NoError(t, err) + err = netB.Start() + require.NoError(t, err) + defer netB.Stop() + + require.Eventually( + t, + func() bool { + return len(netA.service.ListPeersForTopic(p2p.TXTopicName)) > 0 && + len(netB.service.ListPeersForTopic(p2p.TXTopicName)) > 0 + }, + 2*time.Second, + 50*time.Millisecond, + ) + + require.Eventually(t, func() bool { + return netA.hasPeers() && netB.hasPeers() + }, 2*time.Second, 50*time.Millisecond) + + makeCounterHandler := func(numExpected int) ([]TaggedMessageProcessor, *atomic.Uint32, chan struct{}) { + var numActual atomic.Uint32 + counterDone := make(chan struct{}) + counterHandler := []TaggedMessageProcessor{ + { + Tag: protocol.TxnTag, + MessageHandler: struct { + ProcessorValidateFunc + ProcessorHandleFunc + }{ + ProcessorValidateFunc(func(msg IncomingMessage) ValidatedMessage { + return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: nil} + }), + ProcessorHandleFunc(func(msg ValidatedMessage) OutgoingMessage { + if count := numActual.Add(1); int(count) >= numExpected { + close(counterDone) + } + return OutgoingMessage{Action: Ignore} + }), + }, + }, + } + return counterHandler, &numActual, counterDone + } + counterHandler, _, counterDone := makeCounterHandler(1) + netA.RegisterProcessors(counterHandler) + + // send 5 messages from both netB to netA + // since there is no node with listening address set => no messages should be received + for i := 0; i < 5; i++ { + err := netB.Relay(context.Background(), protocol.TxnTag, []byte{1, 2, 3, byte(i)}, true, nil) + require.NoError(t, err) + } + + select { + case <-counterDone: + require.Fail(t, "No messages should have been received") + case <-time.After(1 * time.Second): + } + + // add netC with listening address set, and enable relaying on netB + // ensure all messages are received by netA + cfg.NetAddress = "127.0.0.1:0" + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + require.NoError(t, err) + err = netC.Start() + require.NoError(t, err) + defer netC.Stop() + + netB.relayMessages = true + + require.Eventually( + t, + func() bool { + return len(netA.service.ListPeersForTopic(p2p.TXTopicName)) > 0 && + len(netB.service.ListPeersForTopic(p2p.TXTopicName)) > 0 && + len(netC.service.ListPeersForTopic(p2p.TXTopicName)) > 0 + }, + 2*time.Second, + 50*time.Millisecond, + ) + + require.Eventually(t, func() bool { + return netA.hasPeers() && netB.hasPeers() && netC.hasPeers() + }, 2*time.Second, 50*time.Millisecond) + + const expectedMsgs = 10 + counterHandler, count, counterDone := makeCounterHandler(expectedMsgs) + netA.ClearProcessors() + netA.RegisterProcessors(counterHandler) + + for i := 0; i < expectedMsgs/2; i++ { + err := netB.Relay(context.Background(), protocol.TxnTag, []byte{1, 2, 3, byte(i)}, true, nil) + require.NoError(t, err) + err = netC.Relay(context.Background(), protocol.TxnTag, []byte{11, 12, 10 + byte(i), 14}, true, nil) + require.NoError(t, err) + } + // send some duplicate messages, they should be dropped + for i := 0; i < expectedMsgs/2; i++ { + err := netB.Relay(context.Background(), protocol.TxnTag, []byte{1, 2, 3, byte(i)}, true, nil) + require.NoError(t, err) + } + + select { + case <-counterDone: + case <-time.After(2 * time.Second): + if c := count.Load(); c < expectedMsgs { + require.Failf(t, "One or more messages failed to reach destination network", "%d > %d", expectedMsgs, c) + } else if c > expectedMsgs { + require.Failf(t, "One or more messages that were expected to be dropped, reached destination network", "%d < %d", expectedMsgs, c) + } + } +} + +type mockSubPService struct { + mockService + count atomic.Int64 + otherPeerID peer.ID + shouldNextFail bool +} + +type mockSubscription struct { + peerID peer.ID + shouldNextFail bool +} + +func (m *mockSubscription) Next(ctx context.Context) (*pubsub.Message, error) { + if m.shouldNextFail { + return nil, errors.New("mockSubscription error") + } + return &pubsub.Message{ReceivedFrom: m.peerID}, nil +} +func (m *mockSubscription) Cancel() {} + +func (m *mockSubPService) Subscribe(topic string, val pubsub.ValidatorEx) (p2p.SubNextCancellable, error) { + m.count.Add(1) + otherPeerID := m.otherPeerID + if otherPeerID == "" { + otherPeerID = "mockSubPServicePeerID" + } + return &mockSubscription{peerID: otherPeerID, shouldNextFail: m.shouldNextFail}, nil +} + +// TestP2PWantTXGossip checks txTopicHandleLoop runs as expected on wantTXGossip changes +func TestP2PWantTXGossip(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // cancelled context to trigger subscription.Next to return + ctx, cancel := context.WithCancel(context.Background()) + cancel() + peerID := peer.ID("myPeerID") + mockService := &mockSubPService{mockService: mockService{id: peerID}, shouldNextFail: true} + net := &P2PNetwork{ + service: mockService, + log: logging.TestingLog(t), + ctx: ctx, + nodeInfo: &nopeNodeInfo{}, + } + + // ensure wantTXGossip from false to false is noop + net.wantTXGossip.Store(false) + net.OnNetworkAdvance() + require.Eventually(t, func() bool { net.wg.Wait(); return true }, 1*time.Second, 50*time.Millisecond) + require.Equal(t, int64(0), mockService.count.Load()) + require.False(t, net.wantTXGossip.Load()) + + // ensure wantTXGossip from true (wantTXGossip) to false (nopeNodeInfo) is noop + net.wantTXGossip.Store(true) + net.OnNetworkAdvance() + require.Eventually(t, func() bool { net.wg.Wait(); return true }, 1*time.Second, 50*time.Millisecond) + require.Equal(t, int64(0), mockService.count.Load()) + require.False(t, net.wantTXGossip.Load()) + + // check false to true change triggers subscription + net.wantTXGossip.Store(false) + net.nodeInfo = &participatingNodeInfo{} + net.OnNetworkAdvance() + require.Eventually(t, func() bool { return mockService.count.Load() == 1 }, 1*time.Second, 50*time.Millisecond) + require.True(t, net.wantTXGossip.Load()) + + // check IsParticipating changes wantTXGossip + net.wantTXGossip.Store(true) + net.nodeInfo = &nopeNodeInfo{} + net.config.ForceFetchTransactions = false + net.relayMessages = false + net.OnNetworkAdvance() + require.Eventually(t, func() bool { net.wg.Wait(); return true }, 1*time.Second, 50*time.Millisecond) + require.False(t, net.wantTXGossip.Load()) + + // check ForceFetchTransactions and relayMessages also take effect + net.wantTXGossip.Store(false) + net.nodeInfo = &nopeNodeInfo{} + net.config.ForceFetchTransactions = true + net.relayMessages = false + net.OnNetworkAdvance() + require.Eventually(t, func() bool { return mockService.count.Load() == 2 }, 1*time.Second, 50*time.Millisecond) + require.True(t, net.wantTXGossip.Load()) + + net.wantTXGossip.Store(false) + net.nodeInfo = &nopeNodeInfo{} + net.config.ForceFetchTransactions = false + net.relayMessages = true + net.OnNetworkAdvance() + require.Eventually(t, func() bool { return mockService.count.Load() == 3 }, 1*time.Second, 50*time.Millisecond) + require.True(t, net.wantTXGossip.Load()) + + // ensure empty nodeInfo prevents changing the value + net.wantTXGossip.Store(false) + net.nodeInfo = nil + net.config.ForceFetchTransactions = true + net.relayMessages = true + net.OnNetworkAdvance() + require.Eventually(t, func() bool { net.wg.Wait(); return true }, 1*time.Second, 50*time.Millisecond) + require.False(t, net.wantTXGossip.Load()) + + // check true to true change is noop + net.wantTXGossip.Store(true) + net.nodeInfo = &participatingNodeInfo{} + net.OnNetworkAdvance() + require.Eventually(t, func() bool { return mockService.count.Load() == 3 }, 1*time.Second, 50*time.Millisecond) + require.True(t, net.wantTXGossip.Load()) +} + +func TestMergeP2PAddrInfoResolvedAddresses(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + m1, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN") + require.NoError(t, err) + m2, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb") + require.NoError(t, err) + m3, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/4001/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC") + require.NoError(t, err) + m4, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/4001") + require.NoError(t, err) + + var tests = []struct { + name string + primary []ma.Multiaddr + backup []ma.Multiaddr + expected int + hasInvalid bool + }{ + {"no overlap", []ma.Multiaddr{m1}, []ma.Multiaddr{m2}, 2, false}, + {"complete overlap", []ma.Multiaddr{m1}, []ma.Multiaddr{m1}, 1, false}, + {"partial overlap", []ma.Multiaddr{m1, m2}, []ma.Multiaddr{m1, m3}, 3, false}, + {"empty slices", []ma.Multiaddr{}, []ma.Multiaddr{}, 0, false}, + {"nil slices", nil, nil, 0, false}, + {"invalid p2p", []ma.Multiaddr{m1, m4}, []ma.Multiaddr{m2, m4}, 2, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r1 := mergeP2PMultiaddrResolvedAddresses(tt.primary, tt.backup) + if len(r1) != tt.expected { + t.Errorf("Expected %d addresses, got %d", tt.expected, len(r1)) + } + + var info1 []peer.AddrInfo + var info2 []peer.AddrInfo + for _, addr := range tt.primary { + info, err0 := peer.AddrInfoFromP2pAddr(addr) + if tt.hasInvalid { + if err0 == nil { + info1 = append(info1, *info) + } + } else { + require.NoError(t, err0) + info1 = append(info1, *info) + } + } + for _, addr := range tt.backup { + info, err0 := peer.AddrInfoFromP2pAddr(addr) + if tt.hasInvalid { + if err0 == nil { + info2 = append(info2, *info) + } + } else { + require.NoError(t, err0) + info2 = append(info2, *info) + } + } + if info1 == nil && tt.primary != nil { + info1 = []peer.AddrInfo{} + } + if info2 == nil && tt.backup != nil { + info1 = []peer.AddrInfo{} + } + + r2 := mergeP2PAddrInfoResolvedAddresses(info1, info2) + if len(r2) != tt.expected { + t.Errorf("Expected %d addresses, got %d", tt.expected, len(r2)) + } + }) + } +} + +// TestP2PGossipSubPeerCasts checks that gossipSubPeer implements the ErlClient and IPAddressable interfaces +// needed by TxHandler +func TestP2PGossipSubPeerCasts(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var g interface{} = gossipSubPeer{} + _, ok := g.(util.ErlClient) + require.True(t, ok) + + _, ok = g.(IPAddressable) + require.True(t, ok) + + // check that gossipSubPeer is hashable as ERL wants + var m map[util.ErlClient]struct{} + require.Equal(t, m[gossipSubPeer{}], struct{}{}) + require.Equal(t, m[g.(util.ErlClient)], struct{}{}) +} diff --git a/network/phonebook.go b/network/phonebook/phonebook.go similarity index 96% rename from network/phonebook.go rename to network/phonebook/phonebook.go index 0c431fd2f3..634ca9c16c 100644 --- a/network/phonebook.go +++ b/network/phonebook/phonebook.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package phonebook import ( "math" @@ -55,12 +55,12 @@ type Phonebook interface { // The connection should be established when the waitTime is 0. // It will register a provisional next connection time when the waitTime is 0. // The provisional time should be updated after the connection with UpdateConnectionTime - GetConnectionWaitTime(addr string) (addrInPhonebook bool, + GetConnectionWaitTime(addrOrPeerID string) (addrInPhonebook bool, waitTime time.Duration, provisionalTime time.Time) // UpdateConnectionTime will update the provisional connection time. // Returns true of the addr was in the phonebook - UpdateConnectionTime(addr string, provisionalTime time.Time) bool + UpdateConnectionTime(addrOrPeerID string, provisionalTime time.Time) bool // ReplacePeerList merges a set of addresses with that passed in for networkName // new entries in dnsAddresses are being added @@ -231,8 +231,10 @@ func (e *phonebookImpl) UpdateRetryAfter(addr string, retryAfter time.Time) { // The connection should be established when the waitTime is 0. // It will register a provisional next connection time when the waitTime is 0. // The provisional time should be updated after the connection with UpdateConnectionTime -func (e *phonebookImpl) GetConnectionWaitTime(addr string) (addrInPhonebook bool, +func (e *phonebookImpl) GetConnectionWaitTime(addrOrPeerID string) (addrInPhonebook bool, waitTime time.Duration, provisionalTime time.Time) { + + addr := addrOrPeerID e.lock.Lock() defer e.lock.Unlock() @@ -276,7 +278,8 @@ func (e *phonebookImpl) GetConnectionWaitTime(addr string) (addrInPhonebook bool // UpdateConnectionTime will update the provisional connection time. // Returns true of the addr was in the phonebook -func (e *phonebookImpl) UpdateConnectionTime(addr string, provisionalTime time.Time) bool { +func (e *phonebookImpl) UpdateConnectionTime(addrOrPeerID string, provisionalTime time.Time) bool { + addr := addrOrPeerID e.lock.Lock() defer e.lock.Unlock() diff --git a/network/phonebook_test.go b/network/phonebook/phonebook_test.go similarity index 99% rename from network/phonebook_test.go rename to network/phonebook/phonebook_test.go index 2643e722ea..d603a51a1a 100644 --- a/network/phonebook_test.go +++ b/network/phonebook/phonebook_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package network +package phonebook import ( "testing" diff --git a/network/requestLogger_test.go b/network/requestLogger_test.go index cb1d7b963d..0de6a41c73 100644 --- a/network/requestLogger_test.go +++ b/network/requestLogger_test.go @@ -25,6 +25,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -49,11 +50,12 @@ func TestRequestLogger(t *testing.T) { dl := eventsDetailsLogger{Logger: log, eventReceived: make(chan interface{}, 1), eventIdentifier: telemetryspec.HTTPRequestEvent} log.SetLevel(logging.Level(defaultConfig.BaseLoggerDebugLevel)) netA := &WebsocketNetwork{ - log: dl, - config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: "go-test-network-genesis", - NetworkID: config.Devtestnet, + log: dl, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: "go-test-network-genesis", + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, } netA.config.EnableRequestLogger = true netA.setup() @@ -67,8 +69,8 @@ func TestRequestLogger(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook = MakePhonebook(1, 1*time.Millisecond) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook = phonebook.MakePhonebook(1, 1*time.Millisecond) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer func() { t.Log("stopping B"); netB.Stop(); t.Log("B done") }() diff --git a/network/requestTracker.go b/network/requestTracker.go index c38cc9d2ed..47eba90c7e 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -31,6 +31,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network/addr" ) const ( @@ -94,12 +95,12 @@ func makeTrackerRequest(remoteAddr, remoteHost, remotePort string, createTime ti // - remoteAddr is used otherwise. func (tr *TrackerRequest) remoteAddress() string { if len(tr.otherPublicAddr) != 0 { - url, err := ParseHostOrURL(tr.otherPublicAddr) + url, err := addr.ParseHostOrURL(tr.otherPublicAddr) if err == nil && len(tr.remoteHost) > 0 && url.Hostname() == tr.remoteHost { return tr.otherPublicAddr } } - url, err := ParseHostOrURL(tr.remoteAddr) + url, err := addr.ParseHostOrURL(tr.remoteAddr) if err != nil { // tr.remoteAddr can't be parsed so try to use tr.remoteHost // there is a chance it came from a proxy and has a meaningful value diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index 0a8c934c53..158cf45336 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -27,6 +27,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -86,11 +87,12 @@ func TestRateLimiting(t *testing.T) { // This test is conducted locally, so we want to treat all hosts the same for counting incoming requests. testConfig.DisableLocalhostConnectionRateLimit = false wn := &WebsocketNetwork{ - log: log, - config: testConfig, - phonebook: MakePhonebook(1, 1), - GenesisID: "go-test-network-genesis", - NetworkID: config.Devtestnet, + log: log, + config: testConfig, + phonebook: phonebook.MakePhonebook(1, 1), + GenesisID: "go-test-network-genesis", + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, } // increase the IncomingConnectionsLimit/MaxConnectionsPerIP limits, since we don't want to test these. @@ -115,15 +117,15 @@ func TestRateLimiting(t *testing.T) { clientsCount := int(testConfig.ConnectionsRateLimitingCount + 5) networks := make([]*WebsocketNetwork, clientsCount) - phonebooks := make([]Phonebook, clientsCount) + phonebooks := make([]phonebook.Phonebook, clientsCount) for i := 0; i < clientsCount; i++ { networks[i] = makeTestWebsocketNodeWithConfig(t, noAddressConfig) networks[i].config.GossipFanout = 1 - phonebooks[i] = MakePhonebook(networks[i].config.ConnectionsRateLimitingCount, + phonebooks[i] = phonebook.MakePhonebook(networks[i].config.ConnectionsRateLimitingCount, time.Duration(networks[i].config.ConnectionsRateLimitingWindowSeconds)*time.Second) - phonebooks[i].ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) - networks[i].phonebook = MakePhonebook(1, 1*time.Millisecond) - networks[i].phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + phonebooks[i].ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) + networks[i].phonebook = phonebook.MakePhonebook(1, 1*time.Millisecond) + networks[i].phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) defer func(net *WebsocketNetwork, i int) { t.Logf("stopping network %d", i) net.Stop() @@ -153,7 +155,7 @@ func TestRateLimiting(t *testing.T) { case <-readyCh: // it's closed, so this client got connected. connectedClients++ - phonebookLen := len(phonebooks[i].GetAddresses(1, PhoneBookEntryRelayRole)) + phonebookLen := len(phonebooks[i].GetAddresses(1, phonebook.PhoneBookEntryRelayRole)) // if this channel is ready, than we should have an address, since it didn't get blocked. require.Equal(t, 1, phonebookLen) default: diff --git a/network/websocketProxy_test.go b/network/websocketProxy_test.go index 73298ccd64..96628acb69 100644 --- a/network/websocketProxy_test.go +++ b/network/websocketProxy_test.go @@ -28,6 +28,7 @@ import ( "testing" "time" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/websocket" "github.com/stretchr/testify/require" @@ -71,7 +72,7 @@ func (w *websocketProxy) ServeHTTP(response http.ResponseWriter, request *http.R } // set X-Forwarded-For - url, err := ParseHostOrURL(request.RemoteAddr) + url, err := addr.ParseHostOrURL(request.RemoteAddr) if err != nil { http.Error(response, err.Error(), http.StatusInternalServerError) return @@ -254,7 +255,7 @@ func TestWebsocketProxyWsNet(t *testing.T) { gossipA, err := netA.addrToGossipAddr(addrA) require.NoError(t, err) - parsedA, err := ParseHostOrURL(gossipA) + parsedA, err := addr.ParseHostOrURL(gossipA) require.NoError(t, err) // setup the proxy @@ -316,7 +317,7 @@ func TestWebsocketProxyWsNet(t *testing.T) { peerB := netA.peers[0] require.NotEmpty(t, peerB.originAddress) require.Equal(t, fakeXForwardedFor, peerB.originAddress) - require.NotEqual(t, peerB.RoutingAddr(), peerB.IPAddr()) + require.NotEqual(t, peerB.RoutingAddr(), peerB.ipAddr()) fakeXForwardedForParsed := net.ParseIP(fakeXForwardedFor) require.NotEqual(t, fakeXForwardedForParsed, peerB.RoutingAddr()) } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 4a491f4f9f..8a43ad5234 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -27,6 +27,7 @@ import ( "net/http" "net/textproto" "net/url" + "path" "regexp" "runtime" "strconv" @@ -43,7 +44,11 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" + "github.com/algorand/go-algorand/network/addr" + "github.com/algorand/go-algorand/network/limitcaller" "github.com/algorand/go-algorand/network/limitlistener" + "github.com/algorand/go-algorand/network/p2p" + "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/protocol" tools_network "github.com/algorand/go-algorand/tools/network" "github.com/algorand/go-algorand/tools/network/dnssec" @@ -83,7 +88,7 @@ const httpServerMaxHeaderBytes = 4096 const connectionActivityMonitorInterval = 3 * time.Minute // maxPeerInactivityDuration is the maximum allowed duration for a -// peer to remain completly idle (i.e. no inbound or outbound communication), before +// peer to remain completely idle (i.e. no inbound or outbound communication), before // we discard the connection. const maxPeerInactivityDuration = 5 * time.Minute @@ -163,6 +168,8 @@ const HealthServiceStatusPath = "/status" type NodeInfo interface { // IsParticipating returns true if this node has stake and may vote on blocks or propose blocks. IsParticipating() bool + // Capabilities returns a list of capabilities this node has. + Capabilities() []p2p.Capability } type nopeNodeInfo struct { @@ -172,6 +179,10 @@ func (nnni *nopeNodeInfo) IsParticipating() bool { return false } +func (nnni *nopeNodeInfo) Capabilities() []p2p.Capability { + return nil +} + // WebsocketNetwork implements GossipNode type WebsocketNetwork struct { listener net.Listener @@ -197,12 +208,15 @@ type WebsocketNetwork struct { broadcaster msgBroadcaster handler msgHandler - phonebook Phonebook + phonebook phonebook.Phonebook GenesisID string NetworkID protocol.NetworkID RandomID string + peerID p2p.PeerID + peerIDSigner identityChallengeSigner + ready atomic.Int32 readyChan chan struct{} @@ -238,8 +252,8 @@ type WebsocketNetwork struct { requestsTracker *RequestTracker requestsLogger *RequestLogger - // lastPeerConnectionsSent is the last time the peer connections were sent ( or attempted to be sent ) to the telemetry server. - lastPeerConnectionsSent time.Time + // peerStater collects and report peers connectivity telemetry + peerStater peerConnectionStater // connPerfMonitor is used on outgoing connections to measure their relative message timing connPerfMonitor *connectionPerformanceMonitor @@ -256,8 +270,8 @@ type WebsocketNetwork struct { // transport and dialer are customized to limit the number of // connection in compliance with connectionsRateLimitingCount. - transport rateLimitingTransport - dialer Dialer + transport limitcaller.RateLimitingTransport + dialer limitcaller.Dialer // messagesOfInterest specifies the message types that this node // wants to receive. nil means default. non-nil causes this @@ -348,7 +362,7 @@ type networkPeerManager interface { // used by msgHandler Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error - disconnectThread(badnode Peer, reason disconnectReason) + disconnectThread(badnode DisconnectablePeer, reason disconnectReason) checkPeersConnectivity() } @@ -463,13 +477,13 @@ func (wn *WebsocketNetwork) RelayArray(ctx context.Context, tags []protocol.Tag, return nil } -func (wn *WebsocketNetwork) disconnectThread(badnode Peer, reason disconnectReason) { +func (wn *WebsocketNetwork) disconnectThread(badnode DisconnectablePeer, reason disconnectReason) { defer wn.wg.Done() wn.disconnect(badnode, reason) } // Disconnect from a peer, probably due to protocol errors. -func (wn *WebsocketNetwork) Disconnect(node Peer) { +func (wn *WebsocketNetwork) Disconnect(node DisconnectablePeer) { wn.disconnect(node, disconnectBadData) } @@ -549,16 +563,18 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { case PeersPhonebookRelays: // return copy of phonebook, which probably also contains peers we're connected to, but if it doesn't maybe we shouldn't be making new connections to those peers (because they disappeared from the directory) var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) + addrs = wn.phonebook.GetAddresses(1000, phonebook.PhoneBookEntryRelayRole) for _, addr := range addrs { - peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) + client, _ := wn.GetHTTPClient(addr) + peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, client, "" /*origin address*/) outPeers = append(outPeers, &peerCore) } case PeersPhonebookArchivalNodes: var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchivalRole) + addrs = wn.phonebook.GetAddresses(1000, phonebook.PhoneBookEntryArchivalRole) for _, addr := range addrs { - peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) + client, _ := wn.GetHTTPClient(addr) + peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, client, "" /*origin address*/) outPeers = append(outPeers, &peerCore) } case PeersConnectedIn: @@ -583,13 +599,12 @@ func (wn *WebsocketNetwork) setup() { wn.nodeInfo = &nopeNodeInfo{} } maxIdleConnsPerHost := int(wn.config.ConnectionsRateLimitingCount) - wn.dialer = makeRateLimitingDialer(wn.phonebook, preferredResolver) - wn.transport = makeRateLimitingTransport(wn.phonebook, 10*time.Second, &wn.dialer, maxIdleConnsPerHost) + wn.dialer = limitcaller.MakeRateLimitingDialer(wn.phonebook, preferredResolver) + wn.transport = limitcaller.MakeRateLimitingTransport(wn.phonebook, limitcaller.DefaultQueueingTimeout, &wn.dialer, maxIdleConnsPerHost) wn.upgrader.ReadBufferSize = 4096 wn.upgrader.WriteBufferSize = 4096 wn.upgrader.EnableCompression = false - wn.lastPeerConnectionsSent = time.Now() wn.router = mux.NewRouter() if wn.config.EnableGossipService { wn.router.Handle(GossipNetworkPath, wn) @@ -676,7 +691,7 @@ func (wn *WebsocketNetwork) setup() { } // Start makes network connections and threads -func (wn *WebsocketNetwork) Start() { +func (wn *WebsocketNetwork) Start() error { wn.messagesOfInterestMu.Lock() defer wn.messagesOfInterestMu.Unlock() wn.messagesOfInterestEncoded = true @@ -688,7 +703,7 @@ func (wn *WebsocketNetwork) Start() { listener, err := net.Listen("tcp", wn.config.NetAddress) if err != nil { wn.log.Errorf("network could not listen %v: %s", wn.config.NetAddress, err) - return + return err } // wrap the original listener with a limited connection listener listener = limitlistener.RejectingLimitListener( @@ -720,12 +735,17 @@ func (wn *WebsocketNetwork) Start() { } } } - // if the network has a public address, use that as the name for connection deduplication - if wn.config.PublicAddress != "" { + // if the network has a public address or a libp2p peer ID, use that as the name for connection deduplication + if wn.config.PublicAddress != "" || (wn.peerID != "" && wn.peerIDSigner != nil) { wn.RegisterHandlers(identityHandlers) } - if wn.identityScheme == nil && wn.config.PublicAddress != "" { - wn.identityScheme = NewIdentityChallengeScheme(wn.config.PublicAddress) + if wn.identityScheme == nil { + if wn.peerID != "" && wn.peerIDSigner != nil { + wn.identityScheme = NewIdentityChallengeSchemeWithSigner(string(wn.peerID), wn.peerIDSigner) + } + if wn.config.PublicAddress != "" { + wn.identityScheme = NewIdentityChallengeScheme(wn.config.PublicAddress) + } } wn.meshUpdateRequests <- meshRequest{false, nil} @@ -759,6 +779,8 @@ func (wn *WebsocketNetwork) Start() { go wn.postMessagesOfInterestThread() wn.log.Infof("serving genesisID=%s on %#v with RandomID=%s", wn.GenesisID, wn.PublicAddress(), wn.RandomID) + + return nil } func (wn *WebsocketNetwork) httpdThread() { @@ -842,6 +864,14 @@ func (wn *WebsocketNetwork) ClearHandlers() { wn.handler.ClearHandlers([]Tag{protocol.PingTag, protocol.PingReplyTag, protocol.NetPrioResponseTag}) } +// RegisterProcessors registers the set of given message handlers. +func (wn *WebsocketNetwork) RegisterProcessors(dispatch []TaggedMessageProcessor) { +} + +// ClearProcessors deregisters all the existing message handlers. +func (wn *WebsocketNetwork) ClearProcessors() { +} + func (wn *WebsocketNetwork) setHeaders(header http.Header) { localTelemetryGUID := wn.log.GetTelemetryGUID() localInstanceName := wn.log.GetInstanceName() @@ -1005,7 +1035,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo // request that was provided to the http handler ( or provide a fallback Context() to that ) // if the provided request has no associated connection, it returns nil. ( this should not happen for any http request that was registered // by WebsocketNetwork ) -func (wn *WebsocketNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { +func (wn *WebsocketNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) { if wn.requestsTracker != nil { conn = wn.requestsTracker.GetRequestConnection(request) } @@ -1080,8 +1110,9 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt wn.requestsLogger.SetStatusCode(response, http.StatusSwitchingProtocols) } + client, _ := wn.GetHTTPClient(trackedRequest.remoteAddress()) peer := &wsPeer{ - wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.remoteAddress(), wn.GetRoundTripper(), trackedRequest.remoteHost), + wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.remoteAddress(), client, trackedRequest.remoteHost), conn: wsPeerWebsocketConnImpl{conn}, outgoing: false, InstanceName: trackedRequest.otherInstanceName, @@ -1528,7 +1559,7 @@ func (wn *WebsocketNetwork) isConnectedTo(addr string) bool { wn.peersLock.RLock() defer wn.peersLock.RUnlock() for _, peer := range wn.peers { - if addr == peer.rootURL { + if addr == peer.GetAddress() { return true } } @@ -1605,7 +1636,7 @@ func (wn *WebsocketNetwork) meshThread() { // send the currently connected peers information to the // telemetry server; that would allow the telemetry server // to construct a cross-node map of all the nodes interconnections. - wn.sendPeerConnectionsTelemetryStatus() + wn.peerStater.sendPeerConnectionsTelemetryStatus(wn) } } @@ -1632,12 +1663,12 @@ func (wn *WebsocketNetwork) refreshRelayArchivePhonebookAddresses() { func (wn *WebsocketNetwork) updatePhonebookAddresses(relayAddrs []string, archiveAddrs []string) { if len(relayAddrs) > 0 { wn.log.Debugf("got %d relay dns addrs, %#v", len(relayAddrs), relayAddrs[:imin(5, len(relayAddrs))]) - wn.phonebook.ReplacePeerList(relayAddrs, string(wn.NetworkID), PhoneBookEntryRelayRole) + wn.phonebook.ReplacePeerList(relayAddrs, string(wn.NetworkID), phonebook.PhoneBookEntryRelayRole) } else { wn.log.Infof("got no relay DNS addrs for network %s", wn.NetworkID) } if len(archiveAddrs) > 0 { - wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchivalRole) + wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), phonebook.PhoneBookEntryArchivalRole) } else { wn.log.Infof("got no archive DNS addrs for network %s", wn.NetworkID) } @@ -1656,7 +1687,7 @@ func (wn *WebsocketNetwork) checkNewConnectionsNeeded() bool { return false } // get more than we need so that we can ignore duplicates - newAddrs := wn.phonebook.GetAddresses(desired+numOutgoingTotal, PhoneBookEntryRelayRole) + newAddrs := wn.phonebook.GetAddresses(desired+numOutgoingTotal, phonebook.PhoneBookEntryRelayRole) for _, na := range newAddrs { if na == wn.config.PublicAddress { // filter out self-public address, so we won't try to connect to ourselves. @@ -1767,27 +1798,38 @@ func (wn *WebsocketNetwork) OnNetworkAdvance() { } } +type peerConnectionStater struct { + log logging.Logger + + peerConnectionsUpdateInterval time.Duration + lastPeerConnectionsSent time.Time +} + +type peerSnapshotter interface { + peerSnapshot(peers []*wsPeer) ([]*wsPeer, int32) +} + // sendPeerConnectionsTelemetryStatus sends a snapshot of the currently connected peers // to the telemetry server. Internally, it's using a timer to ensure that it would only // send the information once every hour ( configurable via PeerConnectionsUpdateInterval ) -func (wn *WebsocketNetwork) sendPeerConnectionsTelemetryStatus() { - if !wn.log.GetTelemetryEnabled() { +func (pcs *peerConnectionStater) sendPeerConnectionsTelemetryStatus(snapshotter peerSnapshotter) { + if !pcs.log.GetTelemetryEnabled() { return } now := time.Now() - if wn.lastPeerConnectionsSent.Add(time.Duration(wn.config.PeerConnectionsUpdateInterval)*time.Second).After(now) || wn.config.PeerConnectionsUpdateInterval <= 0 { + if pcs.lastPeerConnectionsSent.Add(pcs.peerConnectionsUpdateInterval).After(now) || pcs.peerConnectionsUpdateInterval <= 0 { // it's not yet time to send the update. return } - wn.lastPeerConnectionsSent = now + pcs.lastPeerConnectionsSent = now var peers []*wsPeer - peers, _ = wn.peerSnapshot(peers) - connectionDetails := wn.getPeerConnectionTelemetryDetails(now, peers) - wn.log.EventWithDetails(telemetryspec.Network, telemetryspec.PeerConnectionsEvent, connectionDetails) + peers, _ = snapshotter.peerSnapshot(peers) + connectionDetails := getPeerConnectionTelemetryDetails(now, peers) + pcs.log.EventWithDetails(telemetryspec.Network, telemetryspec.PeerConnectionsEvent, connectionDetails) } -func (wn *WebsocketNetwork) getPeerConnectionTelemetryDetails(now time.Time, peers []*wsPeer) telemetryspec.PeersConnectionDetails { +func getPeerConnectionTelemetryDetails(now time.Time, peers []*wsPeer) telemetryspec.PeersConnectionDetails { var connectionDetails telemetryspec.PeersConnectionDetails for _, peer := range peers { connDetail := telemetryspec.PeerConnectionDetails{ @@ -2021,10 +2063,31 @@ func (wn *WebsocketNetwork) numOutgoingPending() int { return len(wn.tryConnectAddrs) } -// GetRoundTripper returns an http.Transport that limits the number of connection -// to comply with connectionsRateLimitingCount. -func (wn *WebsocketNetwork) GetRoundTripper() http.RoundTripper { - return &wn.transport +// GetHTTPClient returns a http.Client with a suitable for the network Transport +// that would also limit the number of outgoing connections. +func (wn *WebsocketNetwork) GetHTTPClient(address string) (*http.Client, error) { + return &http.Client{ + Transport: &HTTPPAddressBoundTransport{address, &wn.transport}, + }, nil +} + +// HTTPPAddressBoundTransport is a http.RoundTripper that sets the scheme and host of the request URL to the given address +type HTTPPAddressBoundTransport struct { + Addr string + InnerTransport http.RoundTripper +} + +// RoundTrip implements http.RoundTripper by adding the schema, host, port, path prefix from the +// parsed address to the request URL and then calling the inner transport. +func (t *HTTPPAddressBoundTransport) RoundTrip(req *http.Request) (*http.Response, error) { + url, err := addr.ParseHostOrURL(t.Addr) + if err != nil { + return nil, err + } + req.URL.Scheme = url.Scheme + req.URL.Host = url.Host + req.URL.Path = path.Join(url.Path, req.URL.Path) + return t.InnerTransport.RoundTrip(req) } // filterASCII filter out the non-ascii printable characters out of the given input string and @@ -2045,9 +2108,9 @@ func filterASCII(unfilteredString string) (filteredString string) { } // tryConnect opens websocket connection and checks initial connection parameters. -// addr should be 'host:port' or a URL, gossipAddr is the websocket endpoint URL -func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { - defer wn.tryConnectReleaseAddr(addr, gossipAddr) +// netAddr should be 'host:port' or a URL, gossipAddr is the websocket endpoint URL +func (wn *WebsocketNetwork) tryConnect(netAddr, gossipAddr string) { + defer wn.tryConnectReleaseAddr(netAddr, gossipAddr) defer func() { if xpanic := recover(); xpanic != nil { wn.log.Errorf("panic in tryConnect: %v", xpanic) @@ -2063,7 +2126,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { var idChallenge identityChallengeValue if wn.identityScheme != nil { - idChallenge = wn.identityScheme.AttachChallenge(requestHeader, addr) + idChallenge = wn.identityScheme.AttachChallenge(requestHeader, netAddr) } // for backward compatibility, include the ProtocolVersion header as well. @@ -2108,7 +2171,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { // we've got a retry-after header. // convert it to a timestamp so that we could use it. retryAfterTime := time.Now().Add(time.Duration(retryAfter) * time.Second) - wn.phonebook.UpdateRetryAfter(addr, retryAfterTime) + wn.phonebook.UpdateRetryAfter(netAddr, retryAfterTime) } default: wn.log.Warnf("ws connect(%s) fail - bad handshake, Status code = %d, Headers = %#v, Body = %s", gossipAddr, response.StatusCode, response.Header, errString) @@ -2149,7 +2212,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { peerID, idVerificationMessage, err = wn.identityScheme.VerifyResponse(response.Header, idChallenge) if err != nil { networkPeerIdentityError.Inc(nil) - wn.log.With("err", err).With("remote", addr).With("local", localAddr).Warn("peer supplied an invalid identity response, abandoning peering") + wn.log.With("err", err).With("remote", netAddr).With("local", localAddr).Warn("peer supplied an invalid identity response, abandoning peering") closeEarly("Invalid identity response") return } @@ -2162,8 +2225,9 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { wn.throttledOutgoingConnections.Add(int32(1)) } + client, _ := wn.GetHTTPClient(netAddr) peer := &wsPeer{ - wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /* origin */), + wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, netAddr, client, "" /* origin */), conn: wsPeerWebsocketConnImpl{conn}, outgoing: true, incomingMsgFilter: wn.incomingMsgFilter, @@ -2185,7 +2249,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { wn.peersLock.Unlock() if !ok { networkPeerIdentityDisconnect.Inc(nil) - wn.log.With("remote", addr).With("local", localAddr).Warn("peer deduplicated before adding because the identity is already known") + wn.log.With("remote", netAddr).With("local", localAddr).Warn("peer deduplicated before adding because the identity is already known") closeEarly("Duplicate connection") return } @@ -2193,7 +2257,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { peer.init(wn.config, wn.outgoingMessagesBufferSize) wn.addPeer(peer) - wn.log.With("event", "ConnectedOut").With("remote", addr).With("local", localAddr).Infof("Made outgoing connection to peer %v", addr) + wn.log.With("event", "ConnectedOut").With("remote", netAddr).With("local", localAddr).Infof("Made outgoing connection to peer %v", netAddr) wn.log.EventWithDetails(telemetryspec.Network, telemetryspec.ConnectPeerEvent, telemetryspec.PeerEventDetails{ Address: justHost(conn.RemoteAddr().String()), @@ -2209,7 +2273,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { if len(idVerificationMessage) > 0 { sent := peer.writeNonBlock(context.Background(), idVerificationMessage, true, crypto.Digest{}, time.Now()) if !sent { - wn.log.With("remote", addr).With("local", localAddr).Warn("could not send identity challenge verification") + wn.log.With("remote", netAddr).With("local", localAddr).Warn("could not send identity challenge verification") } } @@ -2224,7 +2288,7 @@ func (wn *WebsocketNetwork) tryConnect(addr, gossipAddr string) { mbytes := append([]byte(protocol.NetPrioResponseTag), resp...) sent := peer.writeNonBlock(context.Background(), mbytes, true, crypto.Digest{}, time.Now()) if !sent { - wn.log.With("remote", addr).With("local", localAddr).Warnf("could not send priority response to %v", addr) + wn.log.With("remote", netAddr).With("local", localAddr).Warnf("could not send priority response to %v", netAddr) } } } @@ -2252,18 +2316,33 @@ func (wn *WebsocketNetwork) SetPeerData(peer Peer, key string, value interface{} } // NewWebsocketNetwork constructor for websockets based gossip network -func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo) (wn *WebsocketNetwork, err error) { - phonebook := MakePhonebook(config.ConnectionsRateLimitingCount, +func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo, peerID p2p.PeerID, idSigner identityChallengeSigner) (wn *WebsocketNetwork, err error) { + pb := phonebook.MakePhonebook(config.ConnectionsRateLimitingCount, time.Duration(config.ConnectionsRateLimitingWindowSeconds)*time.Second) - phonebook.AddPersistentPeers(phonebookAddresses, string(networkID), PhoneBookEntryRelayRole) + + addresses := make([]string, 0, len(phonebookAddresses)) + for _, a := range phonebookAddresses { + _, err := addr.ParseHostOrURL(a) + if err == nil { + addresses = append(addresses, a) + } + } + pb.AddPersistentPeers(addresses, string(networkID), phonebook.PhoneBookEntryRelayRole) wn = &WebsocketNetwork{ log: log, config: config, - phonebook: phonebook, + phonebook: pb, GenesisID: genesisID, NetworkID: networkID, nodeInfo: nodeInfo, + peerID: peerID, + peerIDSigner: idSigner, resolveSRVRecords: tools_network.ReadFromSRV, + peerStater: peerConnectionStater{ + log: log, + peerConnectionsUpdateInterval: time.Duration(config.PeerConnectionsUpdateInterval) * time.Second, + lastPeerConnectionsSent: time.Now(), + }, } wn.setup() @@ -2272,7 +2351,7 @@ func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddre // NewWebsocketGossipNode constructs a websocket network node and returns it as a GossipNode interface implementation func NewWebsocketGossipNode(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID) (gn GossipNode, err error) { - return NewWebsocketNetwork(log, config, phonebookAddresses, genesisID, networkID, nil) + return NewWebsocketNetwork(log, config, phonebookAddresses, genesisID, networkID, nil, "", nil) } // SetPrioScheme specifies the network priority scheme for a network node @@ -2289,11 +2368,11 @@ func (wn *WebsocketNetwork) removePeer(peer *wsPeer, reason disconnectReason) { // first logging, then take the lock and do the actual accounting. // definitely don't change this to do the logging while holding the lock. localAddr, _ := wn.Address() - logEntry := wn.log.With("event", "Disconnected").With("remote", peer.rootURL).With("local", localAddr) + logEntry := wn.log.With("event", "Disconnected").With("remote", peer.GetAddress()).With("local", localAddr) if peer.outgoing && peer.peerMessageDelay > 0 { logEntry = logEntry.With("messageDelay", peer.peerMessageDelay) } - logEntry.Infof("Peer %s disconnected: %s", peer.rootURL, reason) + logEntry.Infof("Peer %s disconnected: %s", peer.GetAddress(), reason) peerAddr := peer.OriginAddress() // we might be able to get addr out of conn, or it might be closed if peerAddr == "" && peer.conn != nil { @@ -2304,12 +2383,12 @@ func (wn *WebsocketNetwork) removePeer(peer *wsPeer, reason disconnectReason) { } if peerAddr == "" { // didn't get addr from peer, try from url - url, err := url.Parse(peer.rootURL) + url, err := url.Parse(peer.GetAddress()) if err == nil { peerAddr = justHost(url.Host) } else { // use whatever it is - peerAddr = justHost(peer.rootURL) + peerAddr = justHost(peer.GetAddress()) } } eventDetails := telemetryspec.PeerEventDetails{ @@ -2493,7 +2572,5 @@ func (wn *WebsocketNetwork) postMessagesOfInterestThread() { } } -// SubstituteGenesisID substitutes the "{genesisID}" with their network-specific genesisID. -func (wn *WebsocketNetwork) SubstituteGenesisID(rawURL string) string { - return strings.Replace(rawURL, "{genesisID}", wn.GenesisID, -1) -} +// GetGenesisID returns the network-specific genesisID. +func (wn *WebsocketNetwork) GetGenesisID() string { return wn.GenesisID } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 8daf4d196f..038a9d6e2d 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -40,6 +40,7 @@ import ( "time" "github.com/algorand/go-algorand/internal/rapidgen" + "github.com/algorand/go-algorand/network/phonebook" "pgregory.net/rapid" "github.com/stretchr/testify/assert" @@ -127,11 +128,12 @@ func makeTestWebsocketNodeWithConfig(t testing.TB, conf config.Local, opts ...te log := logging.TestingLog(t) log.SetLevel(logging.Warn) wn := &WebsocketNetwork{ - log: log, - config: conf, - phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, + log: log, + config: conf, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, } // apply options to newly-created WebsocketNetwork, if provided for _, opt := range opts { @@ -322,7 +324,7 @@ func setupWebsocketNetworkABwithLogger(t *testing.T, countTarget int, log loggin addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer func() { if !success { @@ -458,7 +460,7 @@ func TestWebsocketProposalPayloadCompression(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") messages := [][]byte{ @@ -637,7 +639,7 @@ func TestWebsocketNetworkNoAddress(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -702,7 +704,7 @@ func lineNetwork(t *testing.T, numNodes int) (nodes []*WebsocketNetwork, counter if i > 0 { addrPrev, postListen := nodes[i-1].Address() require.True(t, postListen) - nodes[i].phonebook.ReplacePeerList([]string{addrPrev}, "default", PhoneBookEntryRelayRole) + nodes[i].phonebook.ReplacePeerList([]string{addrPrev}, "default", phonebook.PhoneBookEntryRelayRole) nodes[i].RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: &counters[i]}}) } nodes[i].Start() @@ -1053,11 +1055,12 @@ func makeTestFilterWebsocketNode(t *testing.T, nodename string) *WebsocketNetwor dc.OutgoingMessageFilterBucketCount = 3 dc.OutgoingMessageFilterBucketSize = 128 wn := &WebsocketNetwork{ - log: logging.TestingLog(t).With("node", nodename), - config: dc, - phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, + log: logging.TestingLog(t).With("node", nodename), + config: dc, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: logging.TestingLog(t).With("node", nodename)}, } require.True(t, wn.config.EnableIncomingMessageFilter) wn.setup() @@ -1078,7 +1081,7 @@ func TestDupFilter(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") counter := &messageCounterHandler{t: t, limit: 1, done: make(chan struct{})} @@ -1091,7 +1094,7 @@ func TestDupFilter(t *testing.T) { require.True(t, postListen) netC := makeTestFilterWebsocketNode(t, "c") netC.config.GossipFanout = 1 - netC.phonebook.ReplacePeerList([]string{addrB}, "default", PhoneBookEntryRelayRole) + netC.phonebook.ReplacePeerList([]string{addrB}, "default", phonebook.PhoneBookEntryRelayRole) netC.Start() defer netC.Stop() @@ -1169,8 +1172,8 @@ func TestGetPeers(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - phbMulti := MakePhonebook(1, 1*time.Millisecond) - phbMulti.ReplacePeerList([]string{addrA}, "phba", PhoneBookEntryRelayRole) + phbMulti := phonebook.MakePhonebook(1, 1*time.Millisecond) + phbMulti.ReplacePeerList([]string{addrA}, "phba", phonebook.PhoneBookEntryRelayRole) netB.phonebook = phbMulti netB.Start() defer netB.Stop() @@ -1181,10 +1184,10 @@ func TestGetPeers(t *testing.T) { waitReady(t, netB, readyTimeout.C) t.Log("b ready") - phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", PhoneBookEntryRelayRole) + phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", phonebook.PhoneBookEntryRelayRole) // A few for archival node roles - phbMulti.ReplacePeerList([]string{"d", "e", "f"}, "ph", PhoneBookEntryArchivalRole) + phbMulti.ReplacePeerList([]string{"d", "e", "f"}, "ph", phonebook.PhoneBookEntryArchivalRole) //addrB, _ := netB.Address() @@ -1767,7 +1770,7 @@ func TestPeeringWithBadIdentityChallenge(t *testing.T) { attachChallenge: func(attach http.Header, addr string) identityChallengeValue { s := NewIdentityChallengeScheme("does not matter") // make a scheme to use its keys c := identityChallenge{ - Key: s.identityKeys.SignatureVerifier, + Key: s.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), PublicAddress: []byte("incorrect address!"), } @@ -1785,7 +1788,7 @@ func TestPeeringWithBadIdentityChallenge(t *testing.T) { attachChallenge: func(attach http.Header, addr string) identityChallengeValue { s := NewIdentityChallengeScheme("does not matter") // make a scheme to use its keys c := identityChallenge{ - Key: s.identityKeys.SignatureVerifier, + Key: s.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), PublicAddress: []byte("incorrect address!"), }.Sign(s.identityKeys) @@ -1905,7 +1908,7 @@ func TestPeeringWithBadIdentityChallengeResponse(t *testing.T) { protocol.Decode(msg, &idChal) // make the response object, with an incorrect challenge encode it and attach it to the header r := identityChallengeResponse{ - Key: s.identityKeys.SignatureVerifier, + Key: s.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), ResponseChallenge: newIdentityChallengeValue(), } @@ -1928,7 +1931,7 @@ func TestPeeringWithBadIdentityChallengeResponse(t *testing.T) { protocol.Decode(msg, &idChal) // make the response object, then change the signature and encode and attach r := identityChallengeResponse{ - Key: s.identityKeys.SignatureVerifier, + Key: s.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), ResponseChallenge: newIdentityChallengeValue(), }.Sign(s.identityKeys) @@ -2180,7 +2183,7 @@ func BenchmarkWebsocketNetworkBasic(t *testing.B) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") returns := make(chan uint64, 100) @@ -2262,7 +2265,7 @@ func TestWebsocketNetworkPrio(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -2309,7 +2312,7 @@ func TestWebsocketNetworkPrioLimit(t *testing.T) { netB.SetPrioScheme(&prioB) netB.config.GossipFanout = 1 netB.config.NetAddress = "" - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counterB}}) netB.Start() defer netStop(t, netB, "B") @@ -2323,7 +2326,7 @@ func TestWebsocketNetworkPrioLimit(t *testing.T) { netC.SetPrioScheme(&prioC) netC.config.GossipFanout = 1 netC.config.NetAddress = "" - netC.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netC.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counterC}}) netC.Start() defer func() { t.Log("stopping C"); netC.Stop(); t.Log("C done") }() @@ -2366,8 +2369,8 @@ func TestWebsocketNetworkPrioLimit(t *testing.T) { } if failed { - t.Errorf("NetA had the following two peers priorities : [0]:%s=%d [1]:%s=%d", netA.peers[0].rootURL, netA.peers[0].prioWeight, netA.peers[1].rootURL, netA.peers[1].prioWeight) - t.Errorf("first peer before broadcasting was %s", firstPeer.rootURL) + t.Errorf("NetA had the following two peers priorities : [0]:%s=%d [1]:%s=%d", netA.peers[0].GetAddress(), netA.peers[0].prioWeight, netA.peers[1].GetAddress(), netA.peers[1].prioWeight) + t.Errorf("first peer before broadcasting was %s", firstPeer.GetAddress()) } } @@ -2408,7 +2411,7 @@ func TestWebsocketNetworkManyIdle(t *testing.T) { for i := 0; i < numClients; i++ { client := makeTestWebsocketNodeWithConfig(t, clientConf) client.config.GossipFanout = 1 - client.phonebook.ReplacePeerList([]string{relayAddr}, "default", PhoneBookEntryRelayRole) + client.phonebook.ReplacePeerList([]string{relayAddr}, "default", phonebook.PhoneBookEntryRelayRole) client.Start() defer client.Stop() @@ -2474,7 +2477,7 @@ func TestWebsocketNetwork_checkServerResponseVariables(t *testing.T) { noVersionHeader := http.Header{} noVersionHeader.Set(NodeRandomHeader, wn.RandomID+"tag") noVersionHeader.Set(GenesisHeader, wn.GenesisID) - responseVariableOk, matchingVersion = wn.checkServerResponseVariables(noVersionHeader, "addressX") + responseVariableOk, _ = wn.checkServerResponseVariables(noVersionHeader, "addressX") require.Equal(t, false, responseVariableOk) noRandomHeader := http.Header{} @@ -2533,7 +2536,7 @@ func TestDelayedMessageDrop(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") counter := newMessageCounter(t, 5) @@ -2563,11 +2566,12 @@ func TestSlowPeerDisconnection(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Info) wn := &WebsocketNetwork{ - log: log, - config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, + log: log, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, } wn.setup() wn.broadcaster.slowWritingPeerMonitorInterval = time.Millisecond * 50 @@ -2586,7 +2590,7 @@ func TestSlowPeerDisconnection(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -2638,11 +2642,12 @@ func TestForceMessageRelaying(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Level(defaultConfig.BaseLoggerDebugLevel)) wn := &WebsocketNetwork{ - log: log, - config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, + log: log, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, } wn.setup() wn.eventualReadyDelay = time.Second @@ -2663,14 +2668,14 @@ func TestForceMessageRelaying(t *testing.T) { noAddressConfig.NetAddress = "" netB := makeTestWebsocketNodeWithConfig(t, noAddressConfig) netB.config.GossipFanout = 1 - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") noAddressConfig.ForceRelayMessages = true netC := makeTestWebsocketNodeWithConfig(t, noAddressConfig) netC.config.GossipFanout = 1 - netC.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netC.Start() defer func() { t.Log("stopping C"); netC.Stop(); t.Log("C done") }() @@ -2732,11 +2737,12 @@ func TestCheckProtocolVersionMatch(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Level(defaultConfig.BaseLoggerDebugLevel)) wn := &WebsocketNetwork{ - log: log, - config: defaultConfig, - phonebook: MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, + log: log, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, } wn.setup() wn.supportedProtocolVersions = []string{"2", "1"} @@ -2814,7 +2820,7 @@ func TestWebsocketNetworkTopicRoundtrip(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -2914,7 +2920,7 @@ func TestWebsocketNetworkMessageOfInterest(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Logf("netA %s", addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) // have netB asking netA to send it ft2, deregister ping handler to make sure that we aren't exceeding the maximum MOI messagesize // Max MOI size is calculated by encoding all of the valid tags, since we are using a custom tag here we must deregister one in the default set. @@ -3040,7 +3046,7 @@ func TestWebsocketNetworkTXMessageOfInterestRelay(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -3124,7 +3130,7 @@ func TestWebsocketNetworkTXMessageOfInterestForceTx(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -3206,7 +3212,7 @@ func TestWebsocketNetworkTXMessageOfInterestNPN(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") require.False(t, netB.relayMessages) @@ -3286,6 +3292,7 @@ func TestWebsocketNetworkTXMessageOfInterestNPN(t *testing.T) { } type participatingNodeInfo struct { + nopeNodeInfo } func (nnni *participatingNodeInfo) IsParticipating() bool { @@ -3311,7 +3318,7 @@ func TestWebsocketNetworkTXMessageOfInterestPN(t *testing.T) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") require.False(t, netB.relayMessages) @@ -3433,7 +3440,7 @@ func testWebsocketDisconnection(t *testing.T, disconnectFunc func(wn *WebsocketN addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer netStop(t, netB, "B") @@ -3628,7 +3635,7 @@ func BenchmarkVariableTransactionMessageBlockSizes(t *testing.B) { addrA, postListen := netA.Address() require.True(t, postListen) t.Log(addrA) - netB.phonebook.ReplacePeerList([]string{addrA}, "default", PhoneBookEntryRelayRole) + netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole) netB.Start() defer func() { netB.Stop() }() @@ -3773,9 +3780,9 @@ func TestWebsocketNetworkTelemetryTCP(t *testing.T) { // get RTT from both ends and assert nonzero var peersA, peersB []*wsPeer peersA, _ = netA.peerSnapshot(peersA) - detailsA := netA.getPeerConnectionTelemetryDetails(time.Now(), peersA) + detailsA := getPeerConnectionTelemetryDetails(time.Now(), peersA) peersB, _ = netB.peerSnapshot(peersB) - detailsB := netB.getPeerConnectionTelemetryDetails(time.Now(), peersB) + detailsB := getPeerConnectionTelemetryDetails(time.Now(), peersB) require.Len(t, detailsA.IncomingPeers, 1) assert.NotZero(t, detailsA.IncomingPeers[0].TCP.RTT) require.Len(t, detailsB.OutgoingPeers, 1) @@ -3796,8 +3803,8 @@ func TestWebsocketNetworkTelemetryTCP(t *testing.T) { defer closeFunc2() // use stale peers snapshot from closed networks to get telemetry // *net.OpError "use of closed network connection" err results in 0 rtt values - detailsA = netA.getPeerConnectionTelemetryDetails(time.Now(), peersA) - detailsB = netB.getPeerConnectionTelemetryDetails(time.Now(), peersB) + detailsA = getPeerConnectionTelemetryDetails(time.Now(), peersA) + detailsB = getPeerConnectionTelemetryDetails(time.Now(), peersB) require.Len(t, detailsA.IncomingPeers, 1) assert.Zero(t, detailsA.IncomingPeers[0].TCP.RTT) require.Len(t, detailsB.OutgoingPeers, 1) @@ -4540,3 +4547,75 @@ func TestSendMessageCallbackDrain(t *testing.T) { 50*time.Millisecond, ) } + +// TestWsNetworkPhonebookMix ensures p2p addresses are not added into wsNetwork via phonebook +func TestWsNetworkPhonebookMix(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + net, err := NewWebsocketNetwork( + logging.TestingLog(t), + config.GetDefaultLocal(), + []string{"127.0.0.1:1234", "/ip4/127.0.0.1/tcp/1234", "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"}, + "test", + "net", + nil, + "", + nil, + ) + require.NoError(t, err) + addrs := net.phonebook.GetAddresses(10, phonebook.PhoneBookEntryRelayRole) + require.Len(t, addrs, 1) +} + +type testRecordingTransport struct { + resultURL string +} + +func (rt *testRecordingTransport) RoundTrip(req *http.Request) (*http.Response, error) { + rt.resultURL = req.URL.String() + return &http.Response{StatusCode: 200}, nil +} + +func TestHTTPPAddressBoundTransport(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // first ensure url.URL.String() on path-only URLs works as expected + var url = &url.URL{} + url.Path = "/test" + require.Equal(t, "/test", url.String()) + + // now test some combinations of address and path + const path = "/test/path" + const expErr = "ERR" + tests := []struct { + addr string + expected string + }{ + {"", expErr}, + {":", expErr}, + {"host:1234/lbr", expErr}, + {"host:1234", "http://host:1234" + path}, + {"http://host:1234", "http://host:1234" + path}, + {"http://host:1234/lbr", "http://host:1234/lbr" + path}, + } + + for _, test := range tests { + recorder := testRecordingTransport{} + tr := HTTPPAddressBoundTransport{ + Addr: test.addr, + InnerTransport: &recorder, + } + req, err := http.NewRequest("GET", path, nil) + require.NoError(t, err) + resp, err := tr.RoundTrip(req) + if test.expected == expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + require.Equal(t, test.expected, recorder.resultURL) + } + } +} diff --git a/network/wsPeer.go b/network/wsPeer.go index c4b64bdec4..2b302f071f 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -172,7 +172,7 @@ type wsPeerCore struct { readBuffer chan<- IncomingMessage rootURL string originAddress string // incoming connection remote host - client http.Client + client *http.Client } type disconnectReason string @@ -329,7 +329,6 @@ type HTTPPeer interface { // IPAddressable is addressable with either IPv4 or IPv6 address type IPAddressable interface { - IPAddr() []byte RoutingAddr() []byte } @@ -352,21 +351,20 @@ type TCPInfoUnicastPeer interface { } // Create a wsPeerCore object -func makePeerCore(ctx context.Context, net GossipNode, log logging.Logger, readBuffer chan<- IncomingMessage, rootURL string, roundTripper http.RoundTripper, originAddress string) wsPeerCore { +func makePeerCore(ctx context.Context, net GossipNode, log logging.Logger, readBuffer chan<- IncomingMessage, addr string, client *http.Client, originAddress string) wsPeerCore { return wsPeerCore{ net: net, netCtx: ctx, log: log, readBuffer: readBuffer, - rootURL: rootURL, + rootURL: addr, originAddress: originAddress, - client: http.Client{Transport: roundTripper}, + client: client, } } -// GetAddress returns the root url to use to connect to this peer. -// This implements HTTPPeer interface and used by external services to determine where to connect to. -// TODO: should GetAddress be added to Peer interface? +// GetAddress returns the root url to use to identify or connect to this peer. +// This implements HTTPPeer interface and used to distinguish between peers. func (wp *wsPeerCore) GetAddress() string { return wp.rootURL } @@ -374,7 +372,11 @@ func (wp *wsPeerCore) GetAddress() string { // GetHTTPClient returns a client for this peer. // http.Client will maintain a cache of connections with some keepalive. func (wp *wsPeerCore) GetHTTPClient() *http.Client { - return &wp.client + return wp.client +} + +func (wp *wsPeerCore) GetNetwork() GossipNode { + return wp.net } // Version returns the matching version from network.SupportedProtocolVersions @@ -382,7 +384,7 @@ func (wp *wsPeer) Version() string { return wp.version } -func (wp *wsPeer) IPAddr() []byte { +func (wp *wsPeer) ipAddr() []byte { remote := wp.conn.RemoteAddr() if remote == nil { return nil @@ -417,7 +419,7 @@ func (wp *wsPeer) RoutingAddr() []byte { if wp.wsPeerCore.originAddress != "" { ip = net.ParseIP(wp.wsPeerCore.originAddress) } else { - ip = wp.IPAddr() + ip = wp.ipAddr() } if len(ip) != net.IPv6len { @@ -511,7 +513,7 @@ func (wp *wsPeer) Respond(ctx context.Context, reqMsg IncomingMessage, outMsg Ou // setup values not trivially assigned func (wp *wsPeer) init(config config.Local, sendBufferLength int) { - wp.log.Debugf("wsPeer init outgoing=%v %#v", wp.outgoing, wp.rootURL) + wp.log.Debugf("wsPeer init outgoing=%v %#v", wp.outgoing, wp.GetAddress()) wp.closing = make(chan struct{}) wp.sendBufferHighPrio = make(chan sendMessages, sendBufferLength) wp.sendBufferBulk = make(chan sendMessages, sendBufferLength) diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go index d1f32302a0..973c027b16 100644 --- a/network/wsPeer_test.go +++ b/network/wsPeer_test.go @@ -288,32 +288,32 @@ func TestWsPeerIPAddr(t *testing.T) { } // 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.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.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), 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.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{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()) // check incoming peer with originAddress set conn.addr.IP = []byte{127, 0, 0, 1} peer.wsPeerCore.originAddress = "127.0.0.2" - require.Equal(t, []byte{127, 0, 0, 1}, peer.IPAddr()) + require.Equal(t, []byte{127, 0, 0, 1}, peer.ipAddr()) require.Equal(t, []byte{127, 0, 0, 2}, peer.RoutingAddr()) } diff --git a/node/follower_node.go b/node/follower_node.go index e475b25481..117cc56e86 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -94,7 +94,7 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo node.config = cfg // tie network, block fetcher, and agreement services together - p2pNode, err := network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, nil) + p2pNode, err := network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, nil, "", nil) if err != nil { log.Errorf("could not create websocket node: %v", err) return nil, err @@ -163,7 +163,7 @@ func (node *AlgorandFollowerNode) Config() config.Local { } // Start the node: connect to peers while obtaining a lock. Doesn't wait for initial sync. -func (node *AlgorandFollowerNode) Start() { +func (node *AlgorandFollowerNode) Start() error { node.mu.Lock() defer node.mu.Unlock() @@ -173,22 +173,30 @@ func (node *AlgorandFollowerNode) Start() { // The start network is being called only after the various services start up. // We want to do so in order to let the services register their callbacks with the // network package before any connections are being made. - startNetwork := func() { + startNetwork := func() error { if !node.config.DisableNetworking { // start accepting connections - node.net.Start() + err := node.net.Start() + if err != nil { + return err + } node.config.NetAddress, _ = node.net.Address() } + return nil } + var err error if node.catchpointCatchupService != nil { - startNetwork() - _ = node.catchpointCatchupService.Start(node.ctx) + err = startNetwork() + if err == nil { + err = node.catchpointCatchupService.Start(node.ctx) + } } else { node.catchupService.Start() node.blockService.Start() - startNetwork() + err = startNetwork() } + return err } // ListeningAddress retrieves the node's current listening address, if any. diff --git a/node/node.go b/node/node.go index 384bd258f6..5f1baa56be 100644 --- a/node/node.go +++ b/node/node.go @@ -28,6 +28,8 @@ import ( "sync" "time" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/agreement/gossip" "github.com/algorand/go-algorand/catchup" @@ -47,6 +49,7 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/network/messagetracer" + "github.com/algorand/go-algorand/network/p2p" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/stateproof" @@ -54,7 +57,6 @@ import ( "github.com/algorand/go-algorand/util/execpool" "github.com/algorand/go-algorand/util/metrics" "github.com/algorand/go-algorand/util/timers" - "github.com/algorand/go-deadlock" ) const ( @@ -197,16 +199,21 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd // 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 cfg.EnableP2PHybridMode { + p2pNode, err = network.NewHybridP2PNetwork(node.log, node.config, rootDir, phonebookAddresses, genesis.ID(), genesis.Network, node) + if err != nil { + log.Errorf("could not create hybrid p2p node: %v", err) + return nil, err + } + } else if cfg.EnableP2P { + p2pNode, err = network.NewP2PNetwork(node.log, node.config, rootDir, phonebookAddresses, genesis.ID(), genesis.Network, node) 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) + wsNode, err = network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, node, "", nil) if err != nil { log.Errorf("could not create websocket node: %v", err) return nil, err @@ -340,7 +347,7 @@ func (node *AlgorandFullNode) Config() config.Local { } // Start the node: connect to peers and run the agreement service while obtaining a lock. Doesn't wait for initial sync. -func (node *AlgorandFullNode) Start() { +func (node *AlgorandFullNode) Start() error { node.mu.Lock() defer node.mu.Unlock() @@ -350,12 +357,16 @@ func (node *AlgorandFullNode) Start() { // The start network is being called only after the various services start up. // We want to do so in order to let the services register their callbacks with the // network package before any connections are being made. - startNetwork := func() { + startNetwork := func() error { if !node.config.DisableNetworking { // start accepting connections - node.net.Start() + err := node.net.Start() + if err != nil { + return err + } node.config.NetAddress, _ = node.net.Address() } + return nil } if node.catchpointCatchupService != nil { @@ -369,11 +380,29 @@ func (node *AlgorandFullNode) Start() { node.ledgerService.Start() node.txHandler.Start() node.stateProofWorker.Start() - startNetwork() + err := startNetwork() + if err != nil { + return err + } node.startMonitoringRoutines() } + return nil +} +// Capabilities returns the node's capabilities for advertising to other nodes. +func (node *AlgorandFullNode) Capabilities() []p2p.Capability { + var caps []p2p.Capability + if node.config.Archival { + caps = append(caps, p2p.Archival) + } + if node.config.StoresCatchpoints() { + caps = append(caps, p2p.Catchpoints) + } + if node.config.EnableGossipService && node.config.IsGossipServer() { + caps = append(caps, p2p.Gossip) + } + return caps } // startMonitoringRoutines starts the internal monitoring routines used by the node. diff --git a/node/node_test.go b/node/node_test.go index 54f2e1e6cc..3ea6d4a33d 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -33,19 +33,18 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/data" "github.com/algorand/go-algorand/data/account" "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/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/p2p" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/stateproof" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/db" - "github.com/algorand/go-algorand/util/execpool" ) var expectedAgreementTime = 2*config.Protocol.BigLambda + config.Protocol.SmallLambda + config.Consensus[protocol.ConsensusCurrentVersion].AgreementFilterTimeout + 2*time.Second @@ -61,31 +60,78 @@ var defaultConfig = config.Local{ IncomingConnectionsLimit: -1, } -func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, verificationPool execpool.BacklogPool, customConsensus config.ConsensusProtocols) ([]*AlgorandFullNode, []string) { +type nodeInfo struct { + idx int + host string + wsPort int + p2pPort int + p2pID p2p.PeerID + rootDir string + genesis bookkeeping.Genesis +} + +func (ni nodeInfo) wsNetAddr() string { + return fmt.Sprintf("%s:%d", ni.host, ni.wsPort) +} + +func (ni nodeInfo) p2pNetAddr() string { + return fmt.Sprintf("%s:%d", ni.host, ni.p2pPort) +} + +func (ni nodeInfo) p2pMultiAddr() string { + return fmt.Sprintf("/ip4/%s/tcp/%d/p2p/%s", ni.host, ni.p2pPort, ni.p2pID.String()) +} + +type configHook func(ni nodeInfo, cfg config.Local) (nodeInfo, config.Local) +type phonebookHook func([]nodeInfo, int) []string + +func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, customConsensus config.ConsensusProtocols) ([]*AlgorandFullNode, []string) { + minMoneyAtStart := 10000 + maxMoneyAtStart := 100000 + gen := rand.New(rand.NewSource(2)) + + const numAccounts = 10 + acctStake := make([]basics.MicroAlgos, numAccounts) + for i := range acctStake { + acctStake[i] = basics.MicroAlgos{Raw: uint64(minMoneyAtStart + (gen.Int() % (maxMoneyAtStart - minMoneyAtStart)))} + } + + configHook := func(ni nodeInfo, cfg config.Local) (nodeInfo, config.Local) { + cfg.NetAddress = ni.wsNetAddr() + return ni, cfg + } + + phonebookHook := func(nodes []nodeInfo, nodeIdx int) []string { + phonebook := make([]string, 0, len(nodes)-1) + for i := range nodes { + if i != nodeIdx { + phonebook = append(phonebook, nodes[i].wsNetAddr()) + } + } + return phonebook + } + nodes, wallets := setupFullNodesEx(t, proto, customConsensus, acctStake, configHook, phonebookHook) + require.Len(t, nodes, numAccounts) + require.Len(t, wallets, numAccounts) + return nodes, wallets +} + +func setupFullNodesEx( + t *testing.T, proto protocol.ConsensusVersion, customConsensus config.ConsensusProtocols, + acctStake []basics.MicroAlgos, configHook configHook, phonebookHook phonebookHook, +) ([]*AlgorandFullNode, []string) { + util.SetFdSoftLimit(1000) + f, _ := os.Create(t.Name() + ".log") logging.Base().SetJSONFormatter() logging.Base().SetOutput(f) logging.Base().SetLevel(logging.Debug) - - numAccounts := 10 - minMoneyAtStart := 10000 - maxMoneyAtStart := 100000 + t.Logf("Logging to %s\n", t.Name()+".log") firstRound := basics.Round(0) lastRound := basics.Round(200) - genesis := make(map[basics.Address]basics.AccountData) - gen := rand.New(rand.NewSource(2)) - neighbors := make([]string, numAccounts) - for i := range neighbors { - neighbors[i] = "127.0.0.1:" + strconv.Itoa(10000+i) - } - - wallets := make([]string, numAccounts) - nodes := make([]*AlgorandFullNode, numAccounts) - rootDirs := make([]string, 0) - // The genesis configuration is missing allocations, but that's OK // because we explicitly generated the sqlite database above (in // installFullNode). @@ -97,16 +143,27 @@ func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, verificationP RewardsPool: poolAddr.String(), } + genesis := make(map[basics.Address]basics.AccountData) + numAccounts := len(acctStake) + wallets := make([]string, numAccounts) + nodeInfos := make([]nodeInfo, numAccounts) + for i := range wallets { rootDirectory := t.TempDir() - rootDirs = append(rootDirs, rootDirectory) + nodeInfos[i] = nodeInfo{ + idx: i, + host: "127.0.0.1", + wsPort: 10000 + 100*i, + p2pPort: 10000 + 100*i + 1, + rootDir: rootDirectory, + genesis: g, + } - defaultConfig.NetAddress = neighbors[i] - defaultConfig.SaveToDisk(rootDirectory) + ni, cfg := configHook(nodeInfos[i], defaultConfig) + nodeInfos[i] = ni + cfg.SaveToDisk(rootDirectory) - // Save empty phonebook - we'll add peers after they've been assigned listening ports - err := config.SavePhonebookToDisk(make([]string, 0), rootDirectory) - require.NoError(t, err) + t.Logf("Root directory of node %d (%s): %s\n", i, ni.wsNetAddr(), rootDirectory) genesisDir := filepath.Join(rootDirectory, g.ID()) os.Mkdir(genesisDir, 0700) @@ -140,7 +197,7 @@ func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, verificationP data := basics.AccountData{ Status: basics.Online, - MicroAlgos: basics.MicroAlgos{Raw: uint64(minMoneyAtStart + (gen.Int() % (maxMoneyAtStart - minMoneyAtStart)))}, + MicroAlgos: acctStake[i], SelectionID: part.VRFSecrets().PK, VoteID: part.VotingSecrets().OneTimeSignatureVerifier, } @@ -152,34 +209,37 @@ func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, verificationP MicroAlgos: basics.MicroAlgos{Raw: uint64(100000)}, } - bootstrap := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr) + for addr, data := range genesis { + g.Allocation = append(g.Allocation, bookkeeping.GenesisAllocation{ + Address: addr.String(), + State: bookkeeping.GenesisAccountData{ + Status: data.Status, + MicroAlgos: data.MicroAlgos, + VoteID: data.VoteID, + StateProofID: data.StateProofID, + SelectionID: data.SelectionID, + VoteFirstValid: data.VoteFirstValid, + VoteLastValid: data.VoteLastValid, + VoteKeyDilution: data.VoteKeyDilution, + }, + }) + } - for i, rootDirectory := range rootDirs { + nodes := make([]*AlgorandFullNode, numAccounts) + for i := range nodes { + rootDirectory := nodeInfos[i].rootDir genesisDir := filepath.Join(rootDirectory, g.ID()) - ledgerFilenamePrefix := filepath.Join(genesisDir, config.LedgerFilenamePrefix) if customConsensus != nil { - err := config.SaveConfigurableConsensus(genesisDir, customConsensus) - require.Nil(t, err) + err0 := config.SaveConfigurableConsensus(genesisDir, customConsensus) + require.Nil(t, err0) + err0 = config.LoadConfigurableConsensusProtocols(genesisDir) + require.Nil(t, err0) } - err1 := config.LoadConfigurableConsensusProtocols(genesisDir) - require.Nil(t, err1) - nodeID := fmt.Sprintf("Node%d", i) - const inMem = false - cfg, err := config.LoadConfigFromDisk(rootDirectory) - require.NoError(t, err) - cfg.Archival = true - _, err = data.LoadLedger(logging.Base().With("name", nodeID), ledgerFilenamePrefix, inMem, g.Proto, bootstrap, g.ID(), g.Hash(), cfg) - require.NoError(t, err) - } - for i := range nodes { - var nodeNeighbors []string - nodeNeighbors = append(nodeNeighbors, neighbors[:i]...) - nodeNeighbors = append(nodeNeighbors, neighbors[i+1:]...) - rootDirectory := rootDirs[i] cfg, err := config.LoadConfigFromDisk(rootDirectory) + phonebook := phonebookHook(nodeInfos, i) require.NoError(t, err) - node, err := MakeFull(logging.Base().With("source", t.Name()+strconv.Itoa(i)), rootDirectory, cfg, nodeNeighbors, g) + node, err := MakeFull(logging.Base(), rootDirectory, cfg, phonebook, g) nodes[i] = node require.NoError(t, err) } @@ -190,12 +250,16 @@ func setupFullNodes(t *testing.T, proto protocol.ConsensusVersion, verificationP func TestSyncingFullNode(t *testing.T) { partitiontest.PartitionTest(t) - t.Skip("Flaky in nightly test environment") + if testing.Short() { + t.Skip("Test takes ~50 seconds.") + } - backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) - defer backlogPool.Shutdown() + if (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" && runtime.GOOS != "darwin") && + strings.ToUpper(os.Getenv("CIRCLECI")) == "TRUE" { + t.Skip("Test is too heavy for amd64 builder running in parallel with other packages") + } - nodes, wallets := setupFullNodes(t, protocol.ConsensusCurrentVersion, backlogPool, nil) + nodes, wallets := setupFullNodes(t, protocol.ConsensusCurrentVersion, nil) for i := 0; i < len(nodes); i++ { defer os.Remove(wallets[i]) defer nodes[i].Stop() @@ -203,7 +267,7 @@ func TestSyncingFullNode(t *testing.T) { initialRound := nodes[0].ledger.NextRound() - startAndConnectNodes(nodes, true) + startAndConnectNodes(nodes, defaultFirstNodeStartDelay) counter := 0 for tests := uint64(0); tests < 16; tests++ { @@ -252,22 +316,19 @@ func TestInitialSync(t *testing.T) { t.Skip("Test takes ~25 seconds.") } - if (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") && + if (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" && runtime.GOOS != "darwin") && strings.ToUpper(os.Getenv("CIRCLECI")) == "TRUE" { t.Skip("Test is too heavy for amd64 builder running in parallel with other packages") } - backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) - defer backlogPool.Shutdown() - - nodes, wallets := setupFullNodes(t, protocol.ConsensusCurrentVersion, backlogPool, nil) + nodes, wallets := setupFullNodes(t, protocol.ConsensusCurrentVersion, nil) for i := 0; i < len(nodes); i++ { defer os.Remove(wallets[i]) defer nodes[i].Stop() } initialRound := nodes[0].ledger.NextRound() - startAndConnectNodes(nodes, true) + startAndConnectNodes(nodes, defaultFirstNodeStartDelay) select { case <-nodes[0].ledger.Wait(initialRound): @@ -289,10 +350,14 @@ func TestInitialSync(t *testing.T) { func TestSimpleUpgrade(t *testing.T) { partitiontest.PartitionTest(t) - t.Skip("Flaky in nightly test environment.") + if testing.Short() { + t.Skip("Test takes ~50 seconds.") + } - backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) - defer backlogPool.Shutdown() + if (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" && runtime.GOOS != "darwin") && + strings.ToUpper(os.Getenv("CIRCLECI")) == "TRUE" { + t.Skip("Test is too heavy for amd64 builder running in parallel with other packages") + } // ConsensusTest0 is a version of ConsensusV0 used for testing // (it has different approved upgrade paths). @@ -330,7 +395,7 @@ func TestSimpleUpgrade(t *testing.T) { testParams1.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} configurableConsensus[consensusTest1] = testParams1 - nodes, wallets := setupFullNodes(t, consensusTest0, backlogPool, configurableConsensus) + nodes, wallets := setupFullNodes(t, consensusTest0, configurableConsensus) for i := 0; i < len(nodes); i++ { defer os.Remove(wallets[i]) defer nodes[i].Stop() @@ -338,13 +403,13 @@ func TestSimpleUpgrade(t *testing.T) { initialRound := nodes[0].ledger.NextRound() - startAndConnectNodes(nodes, false) + startAndConnectNodes(nodes, nodelayFirstNodeStartDelay) maxRounds := basics.Round(16) roundsCheckedForUpgrade := 0 for tests := basics.Round(0); tests < maxRounds; tests++ { - blocks := make([]bookkeeping.Block, len(wallets), len(wallets)) + blocks := make([]bookkeeping.Block, len(wallets)) for i := range wallets { select { case <-nodes[i].ledger.Wait(initialRound + tests): @@ -387,10 +452,13 @@ func TestSimpleUpgrade(t *testing.T) { require.Equal(t, 2, roundsCheckedForUpgrade) } -func startAndConnectNodes(nodes []*AlgorandFullNode, delayStartFirstNode bool) { +const defaultFirstNodeStartDelay = 20 * time.Second +const nodelayFirstNodeStartDelay = 0 + +func startAndConnectNodes(nodes []*AlgorandFullNode, delayStartFirstNode time.Duration) { var wg sync.WaitGroup for i := range nodes { - if delayStartFirstNode && i == 0 { + if delayStartFirstNode != nodelayFirstNodeStartDelay && i == 0 { continue } wg.Add(1) @@ -401,9 +469,9 @@ func startAndConnectNodes(nodes []*AlgorandFullNode, delayStartFirstNode bool) { } wg.Wait() - if delayStartFirstNode { + if delayStartFirstNode != nodelayFirstNodeStartDelay { connectPeers(nodes[1:]) - delayStartNode(nodes[0], nodes[1:], 20*time.Second) + delayStartNode(nodes[0], nodes[1:], delayStartFirstNode) } else { connectPeers(nodes) } @@ -754,3 +822,208 @@ func TestMaxSizesCorrect(t *testing.T) { tsSize := uint64(network.MaxMessageLength) require.Equal(t, tsSize, protocol.TopicMsgRespTag.MaxMessageSize()) } + +// TestNodeHybridTopology set ups 3 nodes network with the following topology: +// N -- R -- A and ensures N can discover A and download blocks from it. +// +// N is a non-part node that joins the network later +// R is a non-archival relay node with block service disabled. It MUST NOT service blocks to force N to discover A. +// A is a archival node that can only provide blocks. +// Nodes N and A have only R in their initial phonebook, and all nodes are in hybrid mode. +func TestNodeHybridTopology(t *testing.T) { + partitiontest.PartitionTest(t) + + const consensusTest0 = protocol.ConsensusVersion("test0") + + configurableConsensus := make(config.ConsensusProtocols) + + testParams0 := config.Consensus[protocol.ConsensusCurrentVersion] + testParams0.AgreementFilterTimeoutPeriod0 = 500 * time.Millisecond + configurableConsensus[consensusTest0] = testParams0 + + minMoneyAtStart := 1_000_000 + maxMoneyAtStart := 100_000_000_000 + gen := rand.New(rand.NewSource(2)) + + const numAccounts = 3 + acctStake := make([]basics.MicroAlgos, numAccounts) + for i := range acctStake { + acctStake[i] = basics.MicroAlgos{Raw: uint64(minMoneyAtStart + (gen.Int() % (maxMoneyAtStart - minMoneyAtStart)))} + } + acctStake[0] = basics.MicroAlgos{} // no stake at node 0 + + configHook := func(ni nodeInfo, cfg config.Local) (nodeInfo, config.Local) { + cfg = config.GetDefaultLocal() + if ni.idx != 2 { + cfg.EnableBlockService = false + cfg.EnableGossipBlockService = false + cfg.EnableLedgerService = false + cfg.CatchpointInterval = 0 + cfg.Archival = false + } else { + // node 2 is archival + cfg.EnableBlockService = true + cfg.EnableGossipBlockService = true + cfg.EnableLedgerService = true + cfg.CatchpointInterval = 200 + cfg.Archival = true + } + if ni.idx == 0 { + // do not allow node 0 (N) to make any outgoing connections + cfg.GossipFanout = 0 + } + + cfg.NetAddress = ni.wsNetAddr() + cfg.EnableP2PHybridMode = true + cfg.EnableDHTProviders = true + cfg.P2PPersistPeerID = true + privKey, err := p2p.GetPrivKey(cfg, ni.rootDir) + require.NoError(t, err) + ni.p2pID, err = p2p.PeerIDFromPublicKey(privKey.GetPublic()) + require.NoError(t, err) + + cfg.P2PNetAddress = ni.p2pNetAddr() + return ni, cfg + } + + phonebookHook := func(ni []nodeInfo, i int) []string { + switch i { + case 0: + // node 0 (N) only accept connections at the beginning to learn about archival node from DHT + t.Logf("Node%d phonebook: empty", i) + return []string{} + case 1: + // node 1 (R) connects to all + t.Logf("Node%d phonebook: %s, %s, %s, %s", i, ni[0].wsNetAddr(), ni[2].wsNetAddr(), ni[0].p2pMultiAddr(), ni[2].p2pMultiAddr()) + return []string{ni[0].wsNetAddr(), ni[2].wsNetAddr(), ni[0].p2pMultiAddr(), ni[2].p2pMultiAddr()} + case 2: + // node 2 (A) connects to R + t.Logf("Node%d phonebook: %s, %s", i, ni[1].wsNetAddr(), ni[1].p2pMultiAddr()) + return []string{ni[1].wsNetAddr(), ni[1].p2pMultiAddr()} + default: + t.Errorf("not expected number of nodes: %d", i) + t.FailNow() + } + return nil + } + + nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook) + require.Len(t, nodes, 3) + require.Len(t, wallets, 3) + for i := 0; i < len(nodes); i++ { + defer os.Remove(wallets[i]) + defer nodes[i].Stop() + } + + startAndConnectNodes(nodes, 10*time.Second) + + initialRound := nodes[0].ledger.NextRound() + targetRound := initialRound + 10 + + select { + case <-nodes[0].ledger.Wait(targetRound): + e0, err := nodes[0].ledger.Block(targetRound) + require.NoError(t, err) + e1, err := nodes[1].ledger.Block(targetRound) + require.NoError(t, err) + require.Equal(t, e1.Hash(), e0.Hash()) + case <-time.After(120 * time.Second): + require.Fail(t, fmt.Sprintf("no block notification for wallet: %v.", wallets[0])) + } +} + +// TestNodeP2PRelays creates a network of 3 nodes with the following topology: +// R1 (relay, DHT) -> R2 (relay, phonebook) <- N (part node) +// Expect N to discover R1 via DHT and connect to it. +func TestNodeP2PRelays(t *testing.T) { + partitiontest.PartitionTest(t) + + const consensusTest0 = protocol.ConsensusVersion("test0") + + configurableConsensus := make(config.ConsensusProtocols) + + testParams0 := config.Consensus[protocol.ConsensusCurrentVersion] + testParams0.AgreementFilterTimeoutPeriod0 = 500 * time.Millisecond + configurableConsensus[consensusTest0] = testParams0 + + minMoneyAtStart := 1_000_000 + maxMoneyAtStart := 100_000_000_000 + gen := rand.New(rand.NewSource(2)) + + const numAccounts = 3 + acctStake := make([]basics.MicroAlgos, numAccounts) + // only node N has stake + acctStake[2] = basics.MicroAlgos{Raw: uint64(minMoneyAtStart + (gen.Int() % (maxMoneyAtStart - minMoneyAtStart)))} + + configHook := func(ni nodeInfo, cfg config.Local) (nodeInfo, config.Local) { + cfg = config.GetDefaultLocal() + cfg.BaseLoggerDebugLevel = uint32(logging.Debug) + cfg.EnableP2P = true + cfg.NetAddress = "" + cfg.EnableDHTProviders = true + + cfg.P2PPersistPeerID = true + privKey, err := p2p.GetPrivKey(cfg, ni.rootDir) + require.NoError(t, err) + ni.p2pID, err = p2p.PeerIDFromPublicKey(privKey.GetPublic()) + require.NoError(t, err) + + switch ni.idx { + case 2: + // N is not a relay + default: + cfg.NetAddress = ni.p2pNetAddr() + } + return ni, cfg + } + + phonebookHook := func(ni []nodeInfo, i int) []string { + switch i { + case 0: + // node R1 connects to R2 + t.Logf("Node%d phonebook: %s", i, ni[1].p2pMultiAddr()) + return []string{ni[1].p2pMultiAddr()} + case 1: + // node R2 connects to none one + t.Logf("Node%d phonebook: empty", i) + return []string{} + case 2: + // node N only connects to R1 + t.Logf("Node%d phonebook: %s", i, ni[1].p2pMultiAddr()) + return []string{ni[1].p2pMultiAddr()} + default: + t.Errorf("not expected number of nodes: %d", i) + t.FailNow() + } + return nil + } + + nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook) + require.Len(t, nodes, 3) + require.Len(t, wallets, 3) + for i := 0; i < len(nodes); i++ { + defer os.Remove(wallets[i]) + defer nodes[i].Stop() + } + + startAndConnectNodes(nodes, nodelayFirstNodeStartDelay) + + require.Eventually(t, func() bool { + connectPeers(nodes) + + // since p2p open streams based on peer ID, there is no way to judge + // connectivity based on exact In/Out so count both + return len(nodes[0].net.GetPeers(network.PeersConnectedIn, network.PeersConnectedOut)) >= 1 && + len(nodes[1].net.GetPeers(network.PeersConnectedIn, network.PeersConnectedOut)) >= 2 && + len(nodes[2].net.GetPeers(network.PeersConnectedIn, network.PeersConnectedOut)) >= 1 + }, 60*time.Second, 1*time.Second) + + t.Log("Nodes connected to R2") + + // wait until N gets R1 in its phonebook + require.Eventually(t, func() bool { + // refresh N's peers in order to learn DHT data faster + nodes[2].net.RequestConnectOutgoing(false, nil) + return len(nodes[2].net.GetPeers(network.PeersPhonebookRelays)) == 2 + }, 80*time.Second, 1*time.Second) +} diff --git a/rpcs/blockService.go b/rpcs/blockService.go index d1ef82dfd4..1a9893b70a 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -41,6 +41,7 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/metrics" ) @@ -381,7 +382,6 @@ func (bs *BlockService) handleCatchupReq(ctx context.Context, reqMsg network.Inc return } respTopics, n = topicBlockBytes(bs.log, bs.ledger, basics.Round(round), string(requestType)) - return } // redirectRequest redirects the request to the next round robin fallback endpoint if available @@ -392,18 +392,24 @@ func (bs *BlockService) redirectRequest(round uint64, response http.ResponseWrit return false } - parsedURL, err := network.ParseHostOrURL(peerAddress) - if err != nil { - bs.log.Debugf("redirectRequest: %s", err.Error()) - return false + var redirectURL string + if addr.IsMultiaddr(peerAddress) { + redirectURL = strings.Replace(FormatBlockQuery(round, "", bs.net), "{genesisID}", bs.genesisID, 1) + } else { + parsedURL, err := addr.ParseHostOrURL(peerAddress) + if err != nil { + bs.log.Debugf("redirectRequest: %s", err.Error()) + return false + } + parsedURL.Path = strings.Replace(FormatBlockQuery(round, parsedURL.Path, bs.net), "{genesisID}", bs.genesisID, 1) + redirectURL = parsedURL.String() } - parsedURL.Path = strings.Replace(FormatBlockQuery(round, parsedURL.Path, bs.net), "{genesisID}", bs.genesisID, 1) - http.Redirect(response, request, parsedURL.String(), http.StatusTemporaryRedirect) - bs.log.Debugf("redirectRequest: redirected block request to %s", parsedURL.String()) + http.Redirect(response, request, redirectURL, http.StatusTemporaryRedirect) + bs.log.Debugf("redirectRequest: redirected block request to %s", redirectURL) return true } -// getNextCustomFallbackEndpoint returns the next custorm fallback endpoint in RR ordering +// getNextCustomFallbackEndpoint returns the next custom fallback endpoint in RR ordering func (bs *BlockService) getNextCustomFallbackEndpoint() (endpointAddress string) { if len(bs.fallbackEndpoints.endpoints) == 0 { return @@ -487,7 +493,7 @@ func RawBlockBytes(l LedgerForBlockService, round basics.Round) ([]byte, error) // FormatBlockQuery formats a block request query for the given network and round number func FormatBlockQuery(round uint64, parsedURL string, net network.GossipNode) string { - return net.SubstituteGenesisID(path.Join(parsedURL, "/v1/{genesisID}/block/"+strconv.FormatUint(uint64(round), 36))) + return network.SubstituteGenesisID(net, path.Join(parsedURL, "/v1/{genesisID}/block/"+strconv.FormatUint(uint64(round), 36))) } func makeFallbackEndpoints(log logging.Logger, customFallbackEndpoints string) (fe fallbackEndpoints) { @@ -496,12 +502,16 @@ func makeFallbackEndpoints(log logging.Logger, customFallbackEndpoints string) ( } endpoints := strings.Split(customFallbackEndpoints, ",") for _, ep := range endpoints { - parsed, err := network.ParseHostOrURL(ep) - if err != nil { - log.Warnf("makeFallbackEndpoints: error parsing %s %s", ep, err.Error()) - continue + if addr.IsMultiaddr(ep) { + fe.endpoints = append(fe.endpoints, ep) + } else { + parsed, err := addr.ParseHostOrURL(ep) + if err != nil { + log.Warnf("makeFallbackEndpoints: error parsing %s %s", ep, err.Error()) + continue + } + fe.endpoints = append(fe.endpoints, parsed.String()) } - fe.endpoints = append(fe.endpoints, parsed.String()) } return } diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index 7b1f756f08..e637796adf 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "net/http" + "net/http/httptest" "strings" "sync" "testing" @@ -38,6 +39,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -70,6 +72,10 @@ func (mup *mockUnicastPeer) Respond(ctx context.Context, reqMsg network.Incoming return nil } +func (mup *mockUnicastPeer) GetNetwork() network.GossipNode { + panic("not implemented") +} + // TestHandleCatchupReqNegative covers the error reporting in handleCatchupReq func TestHandleCatchupReqNegative(t *testing.T) { partitiontest.PartitionTest(t) @@ -142,6 +148,8 @@ func TestRedirectFallbackEndpoints(t *testing.T) { net1 := &httpTestPeerSource{} net2 := &httpTestPeerSource{} + net1.GenesisID = "test-genesis-ID" + net2.GenesisID = "test-genesis-ID" nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} @@ -161,7 +169,7 @@ func TestRedirectFallbackEndpoints(t *testing.T) { nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -206,7 +214,7 @@ func TestBlockServiceShutdown(t *testing.T) { nodeA.start() defer nodeA.stop() - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -259,6 +267,8 @@ func TestRedirectOnFullCapacity(t *testing.T) { net1 := &httpTestPeerSource{} net2 := &httpTestPeerSource{} + net1.GenesisID = "test-genesis-ID" + net2.GenesisID = "test-genesis-ID" nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} @@ -286,7 +296,7 @@ func TestRedirectOnFullCapacity(t *testing.T) { nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -445,6 +455,8 @@ func TestRedirectExceptions(t *testing.T) { net1 := &httpTestPeerSource{} net2 := &httpTestPeerSource{} + net1.GenesisID = "{genesisID}" + net2.GenesisID = "{genesisID}" nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} @@ -465,7 +477,7 @@ func TestRedirectExceptions(t *testing.T) { nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} @@ -484,12 +496,13 @@ func TestRedirectExceptions(t *testing.T) { require.NoError(t, err) require.Equal(t, response.StatusCode, http.StatusNotFound) - parsedURLNodeB, err := network.ParseHostOrURL(nodeB.rootURL()) + parsedURLNodeB, err := addr.ParseHostOrURL(nodeB.rootURL()) require.NoError(t, err) parsedURLNodeB.Path = FormatBlockQuery(uint64(4), parsedURLNodeB.Path, net2) blockURLNodeB := parsedURLNodeB.String() requestNodeB, err := http.NewRequest("GET", blockURLNodeB, nil) + require.NoError(t, err) _, err = client.Do(requestNodeB) require.Error(t, err) @@ -548,8 +561,45 @@ func addBlock(t *testing.T, ledger *data.Ledger) (timestamp int64) { func TestErrMemoryAtCapacity(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() macError := errMemoryAtCapacity{capacity: uint64(100), used: uint64(110)} errStr := macError.Error() require.Equal(t, "block service memory over capacity: 110 / 100", errStr) } + +func TestBlockServiceRedirect(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + log := logging.TestingLog(t) + + ep1 := "http://localhost:1234" + ep2 := "/ip4/127.0.0.1/tcp/2345/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN" + endpoints := strings.Join([]string{ep1, ep2}, ",") + fb := makeFallbackEndpoints(log, endpoints) + require.Len(t, fb.endpoints, 2) + require.Equal(t, ep1, fb.endpoints[0]) + require.Equal(t, ep2, fb.endpoints[1]) + + bs := BlockService{ + net: &httpTestPeerSource{}, + fallbackEndpoints: fb, + log: log, + } + + r := httptest.NewRequest("GET", "/", strings.NewReader("")) + w := httptest.NewRecorder() + ok := bs.redirectRequest(10, w, r) + require.True(t, ok) + expectedPath := ep1 + FormatBlockQuery(10, "/", bs.net) + require.Equal(t, expectedPath, w.Result().Header.Get("Location")) + + r = httptest.NewRequest("GET", "/", strings.NewReader("")) + w = httptest.NewRecorder() + ok = bs.redirectRequest(11, w, r) + require.True(t, ok) + // for p2p nodes the url is actually a peer address in p2p network and not part of HTTP path + expectedPath = FormatBlockQuery(11, "", bs.net) + require.Equal(t, expectedPath, w.Result().Header.Get("Location")) +} diff --git a/rpcs/healthService_test.go b/rpcs/healthService_test.go index f2846c322d..c1153d98ba 100644 --- a/rpcs/healthService_test.go +++ b/rpcs/healthService_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) @@ -36,7 +37,7 @@ func TestHealthService_ServeHTTP(t *testing.T) { _ = MakeHealthService(nodeA) - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) client := http.Client{} diff --git a/rpcs/httpTxSync.go b/rpcs/httpTxSync.go index 43258337cb..311a87cf7b 100644 --- a/rpcs/httpTxSync.go +++ b/rpcs/httpTxSync.go @@ -24,7 +24,6 @@ import ( "math/rand" "net/http" "net/url" - "path" "strings" "github.com/algorand/go-algorand/data/bookkeeping" @@ -103,19 +102,18 @@ func (hts *HTTPTxSync) Sync(ctx context.Context, bloom *bloom.Filter) (txgroups if !ok { return nil, fmt.Errorf("cannot HTTPTxSync non http peer %T %#v", peer, peer) } + var syncURL string hts.rootURL = hpeer.GetAddress() + client := hpeer.GetHTTPClient() if client == nil { - client = &http.Client{} - client.Transport = hts.peers.GetRoundTripper() - } - parsedURL, err := network.ParseHostOrURL(hts.rootURL) - if err != nil { - hts.log.Warnf("txSync bad url %v: %s", hts.rootURL, err) - return nil, err + client, err = hts.peers.GetHTTPClient(hts.rootURL) + if err != nil { + return nil, fmt.Errorf("HTTPTxSync cannot create a HTTP client for a peer %T %#v: %s", peer, peer, err.Error()) + } } - parsedURL.Path = hts.peers.SubstituteGenesisID(path.Join(parsedURL.Path, TxServiceHTTPPath)) - syncURL := parsedURL.String() + syncURL = network.SubstituteGenesisID(hts.peers, TxServiceHTTPPath) + hts.log.Infof("http sync from %s", syncURL) params := url.Values{} params.Set("bf", bloomParam) diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go index 5f75a36b0c..823895a417 100644 --- a/rpcs/ledgerService.go +++ b/rpcs/ledgerService.go @@ -60,19 +60,25 @@ type LedgerForService interface { GetCatchpointStream(round basics.Round) (ledger.ReadCloseSizer, error) } +// httpGossipNode is a reduced interface for the gossipNode that only includes the methods needed by the LedgerService +type httpGossipNode interface { + RegisterHTTPHandler(path string, handler http.Handler) + GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettableConn) +} + // 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 atomic.Int32 ledger LedgerForService genesisID string - net network.GossipNode + net httpGossipNode enableService bool stopping sync.WaitGroup } // MakeLedgerService creates a LedgerService around the provider Ledger and registers it with the HTTP router -func MakeLedgerService(config config.Local, ledger LedgerForService, net network.GossipNode, genesisID string) *LedgerService { +func MakeLedgerService(config config.Local, ledger LedgerForService, net httpGossipNode, genesisID string) *LedgerService { service := &LedgerService{ ledger: ledger, genesisID: genesisID, diff --git a/rpcs/ledgerService_test.go b/rpcs/ledgerService_test.go index 1cc52fc9c0..a100f03c2b 100644 --- a/rpcs/ledgerService_test.go +++ b/rpcs/ledgerService_test.go @@ -17,6 +17,9 @@ package rpcs import ( + "archive/tar" + "bytes" + "compress/gzip" "fmt" "io" "net/http" @@ -172,3 +175,59 @@ func TestLedgerService(t *testing.T) { ledgerService.Stop() require.Equal(t, int32(0), ledgerService.running.Load()) } + +type mockSizedStream struct { + *bytes.Buffer +} + +func (mss mockSizedStream) Size() (int64, error) { + return int64(mss.Len()), nil +} + +func (mss mockSizedStream) Close() error { + return nil +} + +type mockLedgerForService struct { +} + +func (l *mockLedgerForService) GetCatchpointStream(round basics.Round) (ledger.ReadCloseSizer, error) { + buf := bytes.NewBuffer(nil) + gz := gzip.NewWriter(buf) + wtar := tar.NewWriter(gz) + wtar.Close() + gz.Close() + + buf2 := bytes.NewBuffer(buf.Bytes()) + return mockSizedStream{buf2}, nil +} + +// TestLedgerServiceP2P creates a ledger service on a node, and a p2p client tries to download +// an empty catchpoint file from the ledger service. +func TestLedgerServiceP2P(t *testing.T) { + partitiontest.PartitionTest(t) + + nodeA, nodeB := nodePairP2p(t) + defer nodeA.Stop() + defer nodeB.Stop() + + genesisID := "test GenesisID" + cfg := config.GetDefaultLocal() + cfg.EnableLedgerService = true + l := mockLedgerForService{} + ledgerService := MakeLedgerService(cfg, &l, nodeA, genesisID) + ledgerService.Start() + defer ledgerService.Stop() + + nodeA.RegisterHTTPHandler(LedgerServiceLedgerPath, ledgerService) + + httpPeer := nodeA.GetHTTPPeer().(network.HTTPPeer) + + req, err := http.NewRequest("GET", fmt.Sprintf("/v1/%s/ledger/0", genesisID), nil) + require.NoError(t, err) + resp, err := httpPeer.GetHTTPClient().Do(req) + require.NoError(t, err) + defer func() { _ = resp.Body.Close() }() + + require.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/rpcs/txService_test.go b/rpcs/txService_test.go index 1e7a5036e4..fcfae1044d 100644 --- a/rpcs/txService_test.go +++ b/rpcs/txService_test.go @@ -22,7 +22,6 @@ import ( "net/http" "net/url" "os" - "strings" "sync" "testing" "time" @@ -34,6 +33,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + p2ptesting "github.com/algorand/go-algorand/network/p2p/testing" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/bloom" ) @@ -64,7 +64,11 @@ func (p testHTTPPeer) GetAddress() string { return string(p) } func (p *testHTTPPeer) GetHTTPClient() *http.Client { - return &http.Client{} + return &http.Client{ + Transport: &network.HTTPPAddressBoundTransport{ + Addr: p.GetAddress(), + InnerTransport: http.DefaultTransport}, + } } func (p *testHTTPPeer) GetHTTPPeer() network.HTTPPeer { return p @@ -116,9 +120,7 @@ func (b *basicRPCNode) GetPeers(options ...network.PeerOption) []network.Peer { return b.peers } -func (b *basicRPCNode) SubstituteGenesisID(rawURL string) string { - return strings.Replace(rawURL, "{genesisID}", "test genesisID", -1) -} +func (b *basicRPCNode) GetGenesisID() string { return "test genesisID" } func nodePair() (*basicRPCNode, *basicRPCNode) { nodeA := &basicRPCNode{} @@ -132,27 +134,84 @@ func nodePair() (*basicRPCNode, *basicRPCNode) { return nodeA, nodeB } +func nodePairP2p(tb testing.TB) (*p2ptesting.HTTPNode, *p2ptesting.HTTPNode) { + nodeA := p2ptesting.MakeHTTPNode(tb) + addrsA := nodeA.Addrs() + require.Greater(tb, len(addrsA), 0) + + nodeB := p2ptesting.MakeHTTPNode(tb) + addrsB := nodeA.Addrs() + require.Greater(tb, len(addrsB), 0) + + nodeA.SetPeers(nodeB) + nodeB.SetPeers(nodeA) + nodeA.SetGenesisID("test genesisID") + nodeB.SetGenesisID("test genesisID") + + nodeA.Start() + nodeB.Start() + + return nodeA, nodeB +} + +// TestTxSync checks txsync on a network with two nodes, A and B func TestTxSync(t *testing.T) { partitiontest.PartitionTest(t) - // A network with two nodes, A and B - nodeA, nodeB := nodePair() - defer nodeA.stop() - defer nodeB.stop() + type txSyncNode interface { + Registrar + network.GossipNode + } - pool := makeMockPendingTxAggregate(3) - RegisterTxService(pool, nodeA, "test genesisID", config.GetDefaultLocal().TxPoolSize, config.GetDefaultLocal().TxSyncServeResponseSize) + tests := []struct { + name string + setup func(t *testing.T) (txSyncNode, txSyncNode, func()) + }{ + { + name: "tcp", + setup: func(t *testing.T) (txSyncNode, txSyncNode, func()) { + nodeA, nodeB := nodePair() + cleanup := func() { + nodeA.stop() + nodeB.stop() + } + return nodeA, nodeB, cleanup + }, + }, + { + name: "p2p", + setup: func(t *testing.T) (txSyncNode, txSyncNode, func()) { + nodeA, nodeB := nodePairP2p(t) + cleanup := func() { + nodeA.Stop() + nodeB.Stop() + } + return nodeA, nodeB, cleanup + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // A network with two nodes, A and B + nodeA, nodeB, cleanupFn := test.setup(t) + defer cleanupFn() - // B tries to fetch block - handler := mockHandler{} - syncInterval := time.Second - syncTimeout := time.Second - syncerPool := makeMockPendingTxAggregate(0) - syncer := MakeTxSyncer(syncerPool, nodeB, &handler, syncInterval, syncTimeout, config.GetDefaultLocal().TxSyncServeResponseSize) - // 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), handler.messageCounter.Load()) + pool := makeMockPendingTxAggregate(3) + RegisterTxService(pool, nodeA, "test genesisID", config.GetDefaultLocal().TxPoolSize, config.GetDefaultLocal().TxSyncServeResponseSize) + + // B tries to fetch block + handler := mockHandler{} + syncInterval := time.Second + syncTimeout := time.Second + syncerPool := makeMockPendingTxAggregate(0) + syncer := MakeTxSyncer(syncerPool, nodeB, &handler, syncInterval, syncTimeout, config.GetDefaultLocal().TxSyncServeResponseSize) + // 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), handler.messageCounter.Load()) + }) + } } func BenchmarkTxSync(b *testing.B) { diff --git a/rpcs/txSyncer_test.go b/rpcs/txSyncer_test.go index 1c3f071879..eb3e4eab60 100644 --- a/rpcs/txSyncer_test.go +++ b/rpcs/txSyncer_test.go @@ -22,7 +22,6 @@ import ( "math/rand" "net/http" "net/rpc" - "strings" "sync/atomic" "testing" "time" @@ -158,6 +157,7 @@ func (client *mockRPCClient) Sync(ctx context.Context, bloom *bloom.Filter) (txg func (client *mockRPCClient) GetAddress() string { return client.rootURL } + func (client *mockRPCClient) GetHTTPClient() *http.Client { return nil } @@ -170,20 +170,13 @@ type mockClientAggregator struct { func (mca *mockClientAggregator) GetPeers(options ...network.PeerOption) []network.Peer { return mca.peers } -func (mca *mockClientAggregator) SubstituteGenesisID(rawURL string) string { - return strings.Replace(rawURL, "{genesisID}", "test genesisID", -1) -} - -const numberOfPeers = 10 -func makeMockClientAggregator(t *testing.T, failWithNil bool, failWithError bool) *mockClientAggregator { - clients := make([]network.Peer, 0) - for i := 0; i < numberOfPeers; i++ { - runner := mockRunner{failWithNil: failWithNil, failWithError: failWithError, done: make(chan *rpc.Call)} - clients = append(clients, &mockRPCClient{client: &runner, log: logging.TestingLog(t)}) - } - t.Logf("len(mca.clients) = %d", len(clients)) - return &mockClientAggregator{peers: clients} +func (mca *mockClientAggregator) GetHTTPClient(address string) (*http.Client, error) { + return &http.Client{ + Transport: &network.HTTPPAddressBoundTransport{ + Addr: address, + InnerTransport: http.DefaultTransport}, + }, nil } func TestSyncFromClient(t *testing.T) { @@ -283,7 +276,7 @@ func TestSync(t *testing.T) { runner := mockRunner{failWithNil: false, failWithError: false, txgroups: pool.PendingTxGroups()[len(pool.PendingTxGroups())-1:], done: make(chan *rpc.Call)} client := mockRPCClient{client: &runner, rootURL: nodeAURL, log: logging.TestingLog(t)} - clientAgg := mockClientAggregator{peers: []network.Peer{&client}} + clientAgg := mockClientAggregator{peers: []network.Peer{&client}, MockNetwork: mocks.MockNetwork{GenesisID: "test genesisID"}} handler := mockHandler{} syncerPool := makeMockPendingTxAggregate(3) syncer := MakeTxSyncer(syncerPool, &clientAgg, &handler, testSyncInterval, testSyncTimeout, config.GetDefaultLocal().TxSyncServeResponseSize) @@ -322,7 +315,7 @@ func TestStartAndStop(t *testing.T) { runner := mockRunner{failWithNil: false, failWithError: false, txgroups: pool.PendingTxGroups()[len(pool.PendingTxGroups())-1:], done: make(chan *rpc.Call)} client := mockRPCClient{client: &runner, rootURL: nodeAURL, log: logging.TestingLog(t)} - clientAgg := mockClientAggregator{peers: []network.Peer{&client}} + clientAgg := mockClientAggregator{peers: []network.Peer{&client}, MockNetwork: mocks.MockNetwork{GenesisID: "test genesisID"}} handler := mockHandler{} syncerPool := makeMockPendingTxAggregate(0) diff --git a/test/heapwatch/agreement-log.py b/test/heapwatch/agreement-log.py new file mode 100644 index 0000000000..4109b37a71 --- /dev/null +++ b/test/heapwatch/agreement-log.py @@ -0,0 +1,187 @@ +""" +Agreement logs parser, takes either separate node.log files from a directory and guessing names from the file names, +or parses the e2e test failure log file watching for node names as " libgoalFixture.go:376: Relay0/node.log:" strings. + +This tool similar a bit to carpenter but takes multiple log files at once. +To force colors when outputting to a file, set FORCE_COLOR=1 in the environment. +""" + +import argparse +from datetime import datetime, timedelta +import glob +import json +import logging +import os +import time + +from termcolor import COLORS, colored + +logger = logging.getLogger(__name__) + +filtered_events = frozenset(['Persisted']) + +def process_json_line(line: str, node_name: str, by_node: dict, events: list): + """Handles a single line of json log file, returns parsed event or None if it's not an agreement event. + + line is a single line of json log file. + node_name is a name of the node that produced this line. + by_node is dict with unique nodes meta information. + events is a list of all parsed events. It is appended in this function to keep the caller code clean. + """ + try: + evt = json.loads(line) + except json.JSONDecodeError: + logger.error('failed to parse json: %s', line) + return None + if evt.get('Context') == 'Agreement' and evt.get('Type'): + if evt['Type'] in filtered_events: + return None + dt = datetime.strptime(evt['time'], '%Y-%m-%dT%H:%M:%S.%f%z') + sender = evt.get('Sender') + sender = sender[:12] if sender else '' + h = evt.get('Hash') + h = h[:8] if h else '' + w = evt.get('Weight', '-') if not evt['Type'].startswith('Proposal') else ' ' + wt = evt.get('WeightTotal', '-') if not evt['Type'].startswith('Proposal') else ' ' + if evt['Type'] in ('StepTimeout', 'VoteAttest', 'BlockAssembled', 'BlockPipelined'): + w, wt = ' ', ' ' + result = { + 'time': dt, + 'type': evt.get('Type'), + 'round': evt.get('Round', '-'), + 'period': evt.get('Period', '-'), + 'step': evt.get('Step', '-'), + 'object_round': evt.get('ObjectRound', '-'), + 'object_period': evt.get('ObjectPeriod', '-'), + 'object_step': evt.get('ObjectStep', '-'), + 'hash': h, + 'sender': sender, + 'weight': w, + 'weight_total': wt, + 'node': node_name, + } + events.append(result) + metadata = by_node.get(node_name) + if not metadata: + metadata = { + 'type': evt.get('Type'), + 'time': dt + } + by_node[node_name] = metadata + else: + if evt.get('Type') == 'RoundConcluded': + rt = dt - metadata['time'] + result['round_time_ms'] = rt / timedelta(milliseconds=1) + elif evt.get('Type') == 'RoundStart': + metadata['time'] = dt + metadata['type'] = 'RoundStart' + by_node[node_name] = metadata + + return result + return None + +def main(): + os.environ['TZ'] = 'UTC' + time.tzset() + + ap = argparse.ArgumentParser() + ap.add_argument('test_log_or_dir', help='Dir with log files or a single log file from e2e tests') + ap.add_argument('-e', '--end-round', type=int, help=f'Round to end at') + args = ap.parse_args() + + by_node = {} + events = [] + if os.path.isdir(args.test_log_or_dir): + logger.info('processing directory %s', args.test_log_or_dir) + log_files = sorted(glob.glob(os.path.join(args.test_log_or_dir, '*-node.log'))) + if not log_files: + logger.error('no log files found in %s', args.test_log_or_dir) + return 1 + for filename in os.listdir(args.test_log_or_dir): + if filename.endswith("-node.log"): + with open(os.path.join(args.test_log_or_dir, filename), 'r') as file: + node_name = filename[:len(filename) - len('-node.log')] + node_name = node_name.replace('relay', 'R') + node_name = node_name.replace('nonParticipatingNode', 'NPN') + node_name = node_name.replace('node', 'N') + for line in file: + event = process_json_line(line, node_name, by_node, events) + if event and args.end_round and \ + isinstance(event['round'], int) and event['round'] >= args.end_round: + break + + else: + logger.info('processing file %s', args.test_log_or_dir) + with open(args.test_log_or_dir, 'r') as file: + line0 = None + while not line0: + line0 = file.readline() + line0 = line0.strip() + + if line0[0] == '{': + # regular json line + node_name = 'node' + process_json_line(line, node_name, by_node, events) + for line in file: + line = line.strip() + event = process_json_line(line, node_name, by_node, events) + if event and args.end_round and \ + isinstance(event['round'], int) and event['round'] >= args.end_round: + break + else: + # looks like e2e test output with lines line this: + """ + libgoalFixture.go:374: ===================... + libgoalFixture.go:376: Relay0/node.log: + libgoalFixture.go:379: {"file":"server.go"... + """ + node_name = None + if line0.endswith('node.log:'): + node_name = line0.split(' ')[1].split('/')[0] + logger.info('found node name: %s', node_name) + for line in file: + line = line.strip() + if line.endswith('node.log:'): + node_name = line.split(' ')[1].split('/')[0] + logger.info('found node name: %s', node_name) + if node_name: + for line in file: + json_start = line.find('{') + if json_start == -1: + # end of continuous json block + node_name = None + break + line = line[json_start:] + event = process_json_line(line, node_name, by_node, events) + if event and args.end_round and \ + isinstance(event['round'], int) and event['round'] >= args.end_round: + break + + log = sorted(events, key=lambda x: x['time']) + + # num_nodes = len(by_node) + colors = list(COLORS) + colors = colors[colors.index('light_grey'):] + if len(colors) < len(by_node): + colors = colors * (len(by_node) // len(colors) + 1) + node_color = {k: v for k, v in zip(by_node.keys(), colors)} + + fmt = '%15s (%s,%s,%s) (%s,%s,%s) %4s|%-4s %-8s %-18s %8s %12s %5s' + print(fmt % ('TS', 'R', 'P', 'S', 'r', 'p', 's', 'W', 'WT', 'NODE', 'EVENT TYPE', 'HASH', 'SENDER', 'RT ms')) + for e in log: + color = node_color[e['node']] + text = colored(fmt % ( + e['time'].strftime('%H:%M:%S.%f'), + e['round'], e['period'], e['step'], + e['object_round'], e['object_period'], e['object_step'], + e['weight'], e['weight_total'], + e['node'][:8], + e['type'], e['hash'], e['sender'], + int(e['round_time_ms']) if 'round_time_ms' in e else ''), + color, + ) + print(text) + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + main() diff --git a/test/heapwatch/block_history_plot.py b/test/heapwatch/block_history_plot.py index 7de45e21b0..d8c86b454f 100644 --- a/test/heapwatch/block_history_plot.py +++ b/test/heapwatch/block_history_plot.py @@ -138,19 +138,21 @@ def process(path, args): min(tpsv[start:end]), max(tpsv[start:end]), )) print('long round times: {}'.format(' '.join(list(map(str,filter(lambda x: x >= 9,dtv[start:end])))))) - fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2,2) - ax1.set_title('round time (seconds)') + fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(2,3, figsize=(10, 5)) + ax1.set_title('round time histogram (sec)') ax1.hist(list(filter(lambda x: x < 9,dtv[start:end])),bins=20) - if args.rtime: - ax2.set_title('round time') - ax2.plot(dtv) - else: - ax2.set_title('TPS') - ax2.hist(tpsv[start:end],bins=20) + ax4.set_title('round time') + ax4.plot(dtv[start:end]) + + ax2.set_title('txn/block histogram') + ax2.hist(txnv[start:end],bins=20) + + ax5.set_title('txn/block') + ax5.plot(txnv[start:end]) - ax3.set_title('txn/block') - ax3.hist(txnv[start:end],bins=20) + ax3.set_title('TPS') + ax3.hist(tpsv[start:end],bins=20) # 10 round moving average TPS tpsv10 = [] @@ -165,12 +167,12 @@ def process(path, args): dtxn = tca-tc0 tpsv10.append(dtxn/dt) if args.tps1: - ax4.set_title('TPS') - ax4.plot(tpsv[start:end]) + ax6.set_title('TPS') + ax6.plot(tpsv[start:end]) print('fullish block sizes: {}'.format(list(filter(lambda x: x > 100, txnv)))) else: - ax4.set_title('TPS(10 round window)') - ax4.plot(tpsv10) + ax6.set_title('TPS(10 round window)') + ax6.plot(tpsv10) fig.tight_layout() plt.savefig(path + '_hist.svg', format='svg') plt.savefig(path + '_hist.png', format='png') diff --git a/test/heapwatch/client_ram_report.py b/test/heapwatch/client_ram_report.py index 97a1171630..f16fbeaa3f 100644 --- a/test/heapwatch/client_ram_report.py +++ b/test/heapwatch/client_ram_report.py @@ -202,6 +202,10 @@ def main(): heap_totals = get_heap_inuse_totals(args.dir) heap_details = get_heap_metrics(args.dir) + if not heap_totals and not heap_details: + print('no data found', file=sys.stderr) + return 0 + if args.csv: if args.csv == '-': csvf = sys.stdout diff --git a/test/heapwatch/metrics_aggs.py b/test/heapwatch/metrics_aggs.py new file mode 100644 index 0000000000..0189634be5 --- /dev/null +++ b/test/heapwatch/metrics_aggs.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019-2024 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 . +# +### +# +# Process and aggregate /metrics data captured by heapWatch.py +# Useful for metrics with labels and bandwidth analysis. +# +import argparse +import glob +import logging +import os +import time +import sys + +import dash +from dash import dcc, html +import plotly.graph_objs as go +from plotly.subplots import make_subplots + + +from metrics_lib import MetricType, parse_metrics, gather_metrics_files_by_nick + +logger = logging.getLogger(__name__) + + +def main(): + os.environ['TZ'] = 'UTC' + time.tzset() + default_img_filename = 'metrics_aggs.png' + default_html_filename = 'metrics_aggs.html' + + 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('-t', '--tags', action='append', default=[], help='tag/label pairs in a=b format to aggregate by, may be repeated. Empty means aggregation by metric name') + ap.add_argument('--nick-re', action='append', default=[], help='regexp to filter node names, may be repeated') + ap.add_argument('--nick-lre', action='append', default=[], help='label:regexp to filter node names, may be repeated') + ap.add_argument('-s', '--save', type=str, choices=['png', 'html'], help=f'save plot to \'{default_img_filename}\' or \'{default_html_filename}\' file instead of showing it') + 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) + + tags = {} + if args.tags: + for tag in args.tags: + if '=' not in tag: + raise (f'Invalid tag: {tag}') + k, v = tag.split('=', 1) + tags[k] = v + tag_keys = set(tags.keys()) + + metrics_files = sorted(glob.glob(os.path.join(args.dir, '*.metrics'))) + metrics_files.extend(glob.glob(os.path.join(args.dir, 'terraform-inventory.host'))) + filesByNick = gather_metrics_files_by_nick(metrics_files, args.nick_re, args.nick_lre) + + 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 = len(metrics_names) + + fig = make_subplots( + rows=nrows, cols=1, + vertical_spacing=0.03, shared_xaxes=True, + subplot_titles=[f'{name}' for name in sorted(metrics_names)], + ) + + fig['layout']['margin'] = { + 'l': 30, 'r': 10, 'b': 10, 't': 20 + } + fig['layout']['height'] = 500 * nrows + + + for nick, files_by_date in filesByNick.items(): + active_metrics = {} + data = {'time': []} + raw_series = {} + raw_times = {} + idx = 0 + for dt, metrics_file in files_by_date.items(): + data['time'].append(dt) + with open(metrics_file, 'rt') as f: + metrics = parse_metrics(f, nick, metrics_names) + for metric_name, metrics_seq in metrics.items(): + active_metric_names = [] + raw_value = 0 + for metric in metrics_seq: + if metric.type != MetricType.COUNTER: + raise RuntimeError('Only COUNT metrics are supported') + if tags is None or tags is not None and metric.has_tags(tag_keys, tags): + raw_value += metric.value + full_name = metric.string(set(tag_keys).union({'n'})) + + if full_name is None: + continue + + if full_name not in data: + # handle gaps in data, sometimes metric file might miss a value + # but the chart requires matching x and y series (time and metric value) + # data is what does into the chart, and raw_series is used to calculate + data[full_name] = [0] * len(files_by_date) + raw_series[full_name] = [] + raw_times[full_name] = [] + + metric_value = raw_value + if len(raw_series[full_name]) > 0 and len(raw_times[full_name]) > 0: + metric_value = (metric_value - raw_series[full_name][-1]) / (dt - raw_times[full_name][-1]).total_seconds() + else: + metric_value = 0 + + data[full_name][idx] = metric_value + raw_series[full_name].append(raw_value) + raw_times[full_name].append(dt) + + active_metric_names.append(full_name) + + active_metric_names.sort() + active_metrics[full_name] = active_metric_names + idx += 1 + + for i, metric_pair in enumerate(sorted(active_metrics.items())): + metric_name, metric_fullnames = metric_pair + for metric_fullname in metric_fullnames: + fig.append_trace(go.Scatter( + x=data['time'], + y=data[metric_fullname], + name=metric_fullname, + mode='lines+markers', + line=dict(width=1), + ), i+1, 1) + + if args.save: + if args.save == 'html': + target_path = os.path.join(args.dir, default_html_filename) + fig.write_html(target_path) + else: + target_path = os.path.join(args.dir, default_img_filename) + fig.write_image(target_path) + print(f'Saved plot to {target_path}') + else: + fig.show() + + return 0 + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/test/heapwatch/metrics_delta.py b/test/heapwatch/metrics_delta.py index 50b1e9e2e3..2d64ee097a 100644 --- a/test/heapwatch/metrics_delta.py +++ b/test/heapwatch/metrics_delta.py @@ -22,7 +22,6 @@ # Generate text report on bandwidth in and out of relays/PN/NPN import argparse -import configparser import contextlib import csv import glob @@ -36,42 +35,10 @@ import sys import time -logger = logging.getLogger(__name__) +from metrics_lib import num, hunum, terraform_inventory_ip_not_names, \ + metric_line_re, test_metric_line_re -def num(x): - if '.' in x: - return float(x) - return int(x) - -def hunum(x): - if x >= 10000000000: - return '{:.1f}G'.format(x / 1000000000.0) - if x >= 1000000000: - return '{:.2f}G'.format(x / 1000000000.0) - if x >= 10000000: - return '{:.1f}M'.format(x / 1000000.0) - if x >= 1000000: - return '{:.2f}M'.format(x / 1000000.0) - if x >= 10000: - return '{:.1f}k'.format(x / 1000.0) - if x >= 1000: - return '{:.2f}k'.format(x / 1000.0) - return '{:.2f}x'.format(x) - -metric_line_re = re.compile(r'(\S+\{[^}]*\})\s+(.*)') - -def test_metric_line_re(): - testlines = ( - ('algod_network_connections_dropped_total{reason="write err"} 1', 1), - #('algod_network_sent_bytes_MS 274992', 274992), # handled by split - ) - for line, n in testlines: - try: - m = metric_line_re.match(line) - assert int(m.group(2)) == n - except: - logger.error('failed on line %r', line, exc_info=True) - raise +logger = logging.getLogger(__name__) def parse_metrics(fin): out = dict() @@ -86,10 +53,15 @@ def parse_metrics(fin): continue m = metric_line_re.match(line) if m: - out[m.group(1)] = num(m.group(2)) + key = m.group(1) + val = m.group(2) else: ab = line.split() - out[ab[0]] = num(ab[1]) + key = ab[0] + val = ab[1] + if key.endswith('{}'): + key = key[:-2] + out[key] = num(val) except: print(f'An exception occurred in parse_metrics: {sys.exc_info()}') pass @@ -371,21 +343,6 @@ 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() @@ -541,7 +498,7 @@ def __init__(self): self.txPLists = {} self.txPSums = {} self.times = [] - # algod_tx_pool_count{} + # algod_tx_pool_count self.txPool = [] # objectBytes = [(curtime, algod_go_memory_classes_heap_objects_bytes), ...] self.objectBytes = [] @@ -601,13 +558,13 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): bi = bisource.get(curtime) if bi is None: logger.warning('%s no blockinfo', path) - self.txPool.append(cur.get('algod_tx_pool_count{}')) + self.txPool.append(cur.get('algod_tx_pool_count')) objectBytes = cur.get('algod_go_memory_classes_heap_objects_bytes') if objectBytes: self.objectBytes.append((curtime, objectBytes)) #logger.debug('%s: %r', path, cur) - verifyGood = cur.get('algod_agreement_proposal_verify_good{}') - verifyMs = cur.get('algod_agreement_proposal_verify_ms{}') + verifyGood = cur.get('algod_agreement_proposal_verify_good') + verifyMs = cur.get('algod_agreement_proposal_verify_ms') if verifyGood and verifyMs: # last writer wins self.verifyMillis = verifyMs / verifyGood @@ -626,8 +583,8 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): rounds = (bi.get('block',{}).get('rnd', 0) - prevbi.get('block',{}).get('rnd', 0)) if rounds != 0: blocktime = dt/rounds - txBytes = d.get('algod_network_sent_bytes_total{}',0) - rxBytes = d.get('algod_network_received_bytes_total{}',0) + txBytes = d.get('algod_network_sent_bytes_total',0) + rxBytes = d.get('algod_network_received_bytes_total',0) txBytesPerSec = txBytes / dt rxBytesPerSec = rxBytes / dt # TODO: gather algod_network_sent_bytes_* and algod_network_received_bytes_* diff --git a/test/heapwatch/metrics_lib.py b/test/heapwatch/metrics_lib.py new file mode 100644 index 0000000000..fbda555b90 --- /dev/null +++ b/test/heapwatch/metrics_lib.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# Copyright (C) 2019-2024 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 . +# +### +# +# Common functions for processing /metrics data captured by heapWatch.py +# +import configparser +from datetime import datetime +from enum import Enum +import logging +import os +import re +import sys +from typing import Dict, Iterable, List, Optional, Tuple, Union +from urllib.parse import urlparse + + +from client_ram_report import dapp + + +logger = logging.getLogger(__name__) +metric_line_re = re.compile(r'(\S+\{[^}]*\})\s+(.*)') + +def num(x): + if '.' in x: + return float(x) + return int(x) + +def hunum(x): + if x >= 10000000000: + return '{:.1f}G'.format(x / 1000000000.0) + if x >= 1000000000: + return '{:.2f}G'.format(x / 1000000000.0) + if x >= 10000000: + return '{:.1f}M'.format(x / 1000000.0) + if x >= 1000000: + return '{:.2f}M'.format(x / 1000000.0) + if x >= 10000: + return '{:.1f}k'.format(x / 1000.0) + if x >= 1000: + return '{:.2f}k'.format(x / 1000.0) + return '{:.2f}x'.format(x) + + +def test_metric_line_re(): + testlines = ( + ('algod_network_connections_dropped_total{reason="write err"} 1', 1), + #('algod_network_sent_bytes_MS 274992', 274992), # handled by split + ) + for line, n in testlines: + try: + m = metric_line_re.match(line) + assert int(m.group(2)) == n + except: + print('failed on line %r', line) + raise + +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 + +metrics_fname_re = re.compile(r'(.*?)\.(\d+_\d+)\.metrics') + +def gather_metrics_files_by_nick( + metrics_files: Iterable[str], nick_res: List[str], nick_lres: List[str] +) -> Dict[str, Dict[datetime, str]]: + """return {"node nickname": {datetime: path, ...}, ...}} + after resolving ip addresses into nodes nick names and applying nick_re and nick_lre filters. + """ + 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) + + if tf_inventory_path: + # remap ip addresses to node names + ip_to_name = terraform_inventory_ip_not_names(tf_inventory_path) + filesByNick2 = {} + for nick in filesByNick.keys(): + parsed = urlparse('//' + nick) + name: str = ip_to_name.get(parsed.hostname) + val = filesByNick[nick] + filesByNick2[name] = val + + filesByNick = filesByNick2 + filesByNick2 = {} + + for nick in filesByNick.keys(): + if nick_res or not nick_res and not nick_lres: + # filter by regexp or apply default renaming + for nick_re in nick_res: + if re.match(nick_re, nick): + break + else: + if nick_res: + # regex is given but not matched, continue to the next node + continue + + # apply default renaming + name = nick + idx = name.find('_') + if idx != -1: + name = name[idx+1:] + val = filesByNick[nick] + filesByNick2[name] = val + + elif nick_lres: + # filter by label:regexp + label = None + for nick_lre in nick_lres: + label, nick_re = nick_lre.split(':') + if re.match(nick_re, nick): + break + else: + if nick_lres: + # regex is given but not matched, continue to the next node + continue + + val = filesByNick[nick] + filesByNick2[label] = val + else: + raise RuntimeError('unexpected options combination') + + if filesByNick2: + filesByNick = filesByNick2 + + return filesByNick + +class MetricType(Enum): + GAUGE = 0 + COUNTER = 1 + +class Metric: + """Metric with tags""" + def __init__(self, metric_name: str, type: MetricType, value: Union[int, float]): + full_name = metric_name.strip() + self.name = full_name + self.value = value + self.type = type + self.tags: Dict[str, str] = {} + self.tag_keys: set = set() + + det_idx = self.name.find('{') + if det_idx != -1: + self.name = self.name[:det_idx] + # ensure that the last character is '}' + idx = full_name.index('}') + if idx != len(full_name) - 1: + raise ValueError(f'Invalid metric name: {full_name}') + raw_tags = full_name[full_name.find('{')+1:full_name.find('}')] + tags = raw_tags.split(',') + for tag in tags: + key, value = tag.split('=') + if value[0] == '"' and value[-1] == '"': + value = value[1:-1] + self.tags[key] = value + self.tag_keys.add(key) + + def short_name(self): + return self.name + + def __str__(self): + return self.string() + + def string(self, tags: Optional[set[str]]=None): + result = self.name + if self.tags: + if not tags: + tags = self.tags + result += '{' + ','.join([f'{k}={v}' for k, v in sorted(self.tags.items()) if k in tags]) + '}' + return result + + def add_tag(self, key: str, value: str): + self.tags[key] = value + self.tag_keys.add(key) + + def has_tags(self, tag_keys: set, tags: Dict[str, str]): + """return True if all tags are present in the metric tags + tag_keys are not strictly needed but used as an optimization + """ + if self.tag_keys.intersection(tag_keys) != tag_keys: + return False + for k, v in tags.items(): + if self.tags.get(k) != v: + return False + return True + +def parse_metrics( + fin: Iterable[str], nick: str, metrics_names: set=None, diff: bool=None +) -> Dict[str, List[Metric]]: + """Parse metrics file and return dicts of metric names (no tags) and list of Metric objects + each containing the metric name, value and tags. + """ + out = {} + 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 = MetricType.GAUGE + elif tpe == 'counter': + last_type = MetricType.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]) + + metric = Metric(name, last_type, value) + metric.add_tag('n', nick) + if not metrics_names or metric.name in metrics_names: + if metric.name not in out: + out[metric.name] = [metric] + else: + out[metric.name].append(metric) + 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]}' + metric = Metric(name, MetricType.GAUGE, out[m[0]].value - out[m[1]].value) + out = [{name: metric}] + + return out diff --git a/test/heapwatch/metrics_viz.py b/test/heapwatch/metrics_viz.py index 584fc0ae59..741aa2dd73 100644 --- a/test/heapwatch/metrics_viz.py +++ b/test/heapwatch/metrics_viz.py @@ -11,13 +11,11 @@ """ 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 @@ -25,95 +23,24 @@ 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 +from metrics_lib import MetricType, parse_metrics, gather_metrics_files_by_nick 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' + default_img_filename = 'metrics_viz.png' + default_html_filename = 'metrics_viz.html' 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('--nick-re', action='append', default=[], help='regexp to filter node names, may be repeated') + ap.add_argument('--nick-lre', action='append', default=[], help='label:regexp to filter node names, may be repeated') + ap.add_argument('-s', '--save', type=str, choices=['png', 'html'], help=f'save plot to \'{default_img_filename}\' or \'{default_html_filename}\' 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') @@ -128,16 +55,8 @@ def main(): 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] + metrics_files.extend(glob.glob(os.path.join(args.dir, 'terraform-inventory.host'))) + filesByNick = gather_metrics_files_by_nick(metrics_files, args.nick_re, args.nick_lre) if args.list_nodes: print('Available nodes:', ', '.join(sorted(filesByNick.keys()))) @@ -156,50 +75,76 @@ def main(): fig = make_subplots( rows=nrows, cols=1, - vertical_spacing=0.03, shared_xaxes=True) + vertical_spacing=0.03, shared_xaxes=True, + subplot_titles=[f'{name}' for name in sorted(metrics_names)], + ) fig['layout']['margin'] = { - 'l': 30, 'r': 10, 'b': 10, 't': 10 + 'l': 30, 'r': 10, 'b': 10, 't': 20 } 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(): + for nick, files_by_date in filesByNick.items(): + active_metrics = {} + data = {'time': []} + raw_series = {} + raw_times = {} + idx = 0 + for dt, metrics_file in files_by_date.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) + metrics = parse_metrics(f, nick, metrics_names, args.diff) + for metric_name, metrics_seq in metrics.items(): + active_metric_names = [] + for metric in metrics_seq: + raw_value = metric.value + + full_name = metric.string() + if full_name not in data: + # handle gaps in data, sometimes metric file might miss a value + # but the chart requires matching x and y series (time and metric value) + # data is what does into the chart, and raw_series is used to calculate + data[full_name] = [0] * len(files_by_date) + raw_series[full_name] = [] + raw_times[full_name] = [] + + metric_value = metric.value + if metric.type == MetricType.COUNTER: + if len(raw_series[full_name]) > 0 and len(raw_times[full_name]) > 0: + metric_value = (metric_value - raw_series[full_name][-1]) / (dt - raw_times[full_name][-1]).total_seconds() + else: + metric_value = 0 + + data[full_name][idx] = metric_value + raw_series[full_name].append(raw_value) + raw_times[full_name].append(dt) + + active_metric_names.append(full_name) + + active_metric_names.sort() + active_metrics[metric_name] = active_metric_names + idx += 1 + + for i, metric_pair in enumerate(sorted(active_metrics.items())): + metric_name, metric_fullnames = metric_pair + for metric_fullname in metric_fullnames: + fig.append_trace(go.Scatter( + x=data['time'], + y=data[metric_fullname], + name=metric_fullname, + mode='lines+markers', + line=dict(width=1), + ), i+1, 1) if args.save: - fig.write_image(os.path.join(args.dir, default_output_file)) + if args.save == 'html': + target_path = os.path.join(args.dir, default_html_filename) + fig.write_html(target_path) + else: + target_path = os.path.join(args.dir, default_img_filename) + fig.write_image(target_path) + print(f'Saved plot to {target_path}') else: fig.show() diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt index d4d68874dd..db92372c6d 100644 --- a/test/heapwatch/requirements.txt +++ b/test/heapwatch/requirements.txt @@ -5,3 +5,6 @@ matplotlib==3.7.2 plotly==5.16.0 py-algorand-sdk==2.3.0 kaleido==0.2.1 +networkx==3.3 +gravis=0.1.0 +termcolor=2.4.0 diff --git a/test/heapwatch/topology-extract-p2p.py b/test/heapwatch/topology-extract-p2p.py new file mode 100644 index 0000000000..41f2be9ffc --- /dev/null +++ b/test/heapwatch/topology-extract-p2p.py @@ -0,0 +1,104 @@ +""" +P2P network topology extraction script from node.log files. + +1. Run P2P scenario like scenario1s-p2p +2. Fetch logs with `algonet play fetch_node_logs` +3. Extract logs +``` +cd nodelog +find . -name 'nodelog.tar.gz' -print | xargs -I{} tar -zxf {} +``` +4. Run this script `python3 topology-extract-p2p.py -o top.json nodelog` +5. Run the visualizer `topology-viz.py top.json` +""" +import argparse +from datetime import datetime +import json +import re +import os +import sys + + +def main(): + # Regex patterns to find node IDs and connections + node_pattern = r"P2P host created: peer ID (\w{52})" + edge_pattern = r"Made outgoing connection to peer (\w{52})" + + ap = argparse.ArgumentParser() + ap.add_argument('log_dir_path', help='logs directory path') + ap.add_argument('-o', '--output', type=argparse.FileType('wt', encoding='utf-8'), help=f'save topology to the file specified instead of showing it') + ap.add_argument('-t', '--timestamp', action='store_true', help=f'store connection timestamp for each edge') + + args = ap.parse_args() + + # Directory containing log files + log_dir_path = args.log_dir_path + + nodes = [] + edges = [] + mapping = {} + + # Iterate through all files in the specified directory + for filename in os.listdir(log_dir_path): + if filename.endswith("-node.log"): + with open(os.path.join(log_dir_path, filename), 'r') as file: + mapped = filename[:len(filename) - len('-node.log')] + mapped = mapped.replace('relay', 'R') + mapped = mapped.replace('nonParticipatingNode', 'NPN') + mapped = mapped.replace('node', 'N') + node_id = None + for line in file: + # Check if line contains relevant substrings before parsing as JSON + if "P2P host created" in line or "Made outgoing connection to peer" in line: + data = json.loads(line.strip()) + + # Check for node creation + if "P2P host created" in data.get("msg", ""): + match = re.search(node_pattern, data["msg"]) + if match: + node_id = match.group(1) + nodes.append(node_id) + mapping[node_id] = mapped + + # Check for connections + elif "Made outgoing connection to peer" in data.get("msg", ""): + match = re.search(edge_pattern, data["msg"]) + if match: + target_node_id = match.group(1) + match = re.findall(r"/p2p/(\w{52})", data["local"]) + if match: + source_node_id = match[0] + else: + print('WARN: no local addr set', data, file=sys.stderr) + source_node_id = node_id + + if args.timestamp: + # datetime is not serializable, so we store it as string for now + edge = (source_node_id, target_node_id, {'dt': data["time"]}) + else: + edge = (source_node_id, target_node_id) + + edges.append(edge) + + result = { + "mapping": mapping, + "nodes": nodes, + "edges": edges + } + + if args.timestamp and not args.output: + edges = sorted(edges, key=lambda x: x[2]['dt']) + for edge in edges: + ts = datetime.strptime(edge[2]['dt'], "%Y-%m-%dT%H:%M:%S.%f%z") + print('%15s %5s -> %-5s' % (ts.strftime('%H:%M:%S.%f'), mapping[edge[0]], mapping[edge[1]])) + return + + if args.output: + json.dump(result, args.output, indent=2) + else: + json.dump(result, sys.stdout, indent=2) + print(file=sys.stdout) + + +if __name__ == '__main__': + main() diff --git a/test/heapwatch/topology-extract-ws.py b/test/heapwatch/topology-extract-ws.py new file mode 100644 index 0000000000..75f1d99f57 --- /dev/null +++ b/test/heapwatch/topology-extract-ws.py @@ -0,0 +1,115 @@ +""" +WSNet network topology extraction script from node.log files. + +1. Run cluster scenario like scenario1s +2. Fetch logs with `algonet play fetch_node_logs` +3. Extract logs +``` +cd nodelog +find . -name 'nodelog.tar.gz' -print | xargs -I{} tar -zxf {} +``` +4. Run this script `python3 topology-extract-ws.py -o top.json -i ../terraform-inventory.json nodelog` +5. Run the visualizer `topology-viz.py top.json` +""" +import argparse +from datetime import datetime +import json +import os +import sys + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('log_dir_path', help='logs directory path') + ap.add_argument('-i', '--inventory-file', type=argparse.FileType('rt', encoding='utf-8'), required=True, help='terraform inventory file path') + ap.add_argument('-o', '--output', type=argparse.FileType('wt', encoding='utf-8'), help=f'save topology to the file specified instead of showing it') + ap.add_argument('-t', '--timestamp', action='store_true', help=f'store connection timestamp for each edge') + + args = ap.parse_args() + + # Directory containing log files + log_dir_path = args.log_dir_path + inventory_file = args.inventory_file + + nodes = [] + edges = [] + mapping = {} + + inventory = json.load(inventory_file) + + ip_to_name = {} + for k, v in inventory.items(): + if k.startswith('name_'): + name = k.split('_')[1].upper() + if not isinstance(v, list) or len(v) != 1: + raise RuntimeError(f"Invalid inventory entry, expected a single item list: {k}={v}") + ip = v[0] + ip_to_name[ip] = name + # no need for mapping but keep the data compatible with the topology-viz script + mapping[name] = name + + # Iterate through all files in the specified directory + for filename in os.listdir(log_dir_path): + if filename.endswith('-node.log'): + with open(os.path.join(log_dir_path, filename), 'r') as file: + mapped = filename[:len(filename) - len('-node.log')] + mapped = mapped.replace('relay', 'R') + mapped = mapped.replace('nonParticipatingNode', 'NPN') + mapped = mapped.replace('node', 'N') + nodes.append(mapped) + for line in file: + # Check if line contains relevant substrings before parsing as JSON + if "Accepted incoming connection from peer" in line or "Made outgoing connection to peer" in line: + data = json.loads(line.strip()) + + # Check for incoming connections + if "Accepted incoming connection from peer" in data.get("msg", ""): + remote = data['remote'] + remote_ip = remote.split(':')[0] + remote_name = ip_to_name[remote_ip] + source = remote_name + target = mapped + edges.append((source, target)) + + # Check for outgoing connections + elif "Made outgoing connection to peer" in data.get('msg', ""): + remote = data['remote'] + name: str = remote.split('.')[0] + # check ip or name + if name.isdigit(): + remote_ip = remote.split(':')[0] + remote_name = ip_to_name[remote_ip] + target = remote_name + source = mapped + else: + target = name.upper() + source = mapped + + if args.timestamp: + # datetime is not serializable, so we store it as string for now + edge = (source, target, {'dt': data["time"]}) + else: + edge = (source, target) + + edges.append(edge) + + result = { + "mapping": mapping, + "nodes": nodes, + "edges": edges + } + + if args.timestamp and not args.output: + edges = sorted(edges, key=lambda x: x[2]['dt']) + for edge in edges: + ts = datetime.strptime(edge[2]['dt'], "%Y-%m-%dT%H:%M:%S.%f%z") + print('%15s %5s -> %-5s' % (ts.strftime('%H:%M:%S.%f'), edge[0], edge[1])) + return + + if args.output: + json.dump(result, args.output, indent=2) + else: + json.dump(result, sys.stdout, indent=2) + print(file=sys.stdout) + +if __name__ == '__main__': + main() diff --git a/test/heapwatch/topology-viz.py b/test/heapwatch/topology-viz.py new file mode 100644 index 0000000000..1393421696 --- /dev/null +++ b/test/heapwatch/topology-viz.py @@ -0,0 +1,75 @@ +""" +P2P network topology visualization script. +See topology-extract-p2p[-ws].py for details. +""" +import argparse +import json +import sys + +import gravis as gv +import networkx as nx + +ap = argparse.ArgumentParser() +ap.add_argument('topology_filename', help='topology json file') +ap.add_argument('-o', '--output', type=argparse.FileType('wt', encoding='utf-8'), help=f'save plot to the file specified instead of showing it') + +args = ap.parse_args() + +with open(args.topology_filename, 'rt') as f: + topology = json.load(f) + +# Create a new directed graph +G = nx.DiGraph() + +G.add_edges_from(topology['edges']) +nx.relabel_nodes(G, topology['mapping'], copy=False) + +# Set node colors +for node in G: + if node.startswith('R'): + G.nodes[node]['color'] = 'red' + elif node.startswith('NPN'): + G.nodes[node]['color'] = 'blue' + elif node.startswith('N'): + G.nodes[node]['color'] = 'green' + else: + raise RuntimeError(f"Unknown node type: {node}") + +# Calculate in-degrees +in_degrees = dict(G.in_degree()) +out_degrees = dict(G.out_degree()) +degree_centrality = nx.degree_centrality(G) +load_centrality = nx.algorithms.load_centrality(G) + +for node in G: + size = max(2, in_degrees[node]) + G.nodes[node]['size'] = size + G.nodes[node]['in_degree'] = in_degrees[node] + G.nodes[node]['out_degree'] = out_degrees[node] + hover = f'In: {in_degrees[node]}, Out: {out_degrees[node]}' + hover += f'\nDegree centrality: {degree_centrality[node]:.2f}' + hover += f'\nLoad centrality: {load_centrality[node]:.2f}' + G.nodes[node]['hover'] = hover + +print('Transitivity:', nx.transitivity(G)) +print('Clustering coefficient:', nx.average_clustering(G)) +print('Avg shortest path length:', nx.average_shortest_path_length(G.to_undirected(as_view=True))) + +res = gv.d3( + G, + node_hover_tooltip=True, + node_size_data_source='size', + node_label_size_factor=0.5, + use_node_size_normalization=True, + node_size_normalization_max=20, + use_edge_size_normalization=True, + edge_curvature=0.1 + ) + +if not args.output: + res.display() + sys.exit(0) + +# Save to file +data = res.to_html() +args.output.write(data) diff --git a/test/testdata/configs/config-v34.json b/test/testdata/configs/config-v34.json index 4a9714115f..7f16155303 100644 --- a/test/testdata/configs/config-v34.json +++ b/test/testdata/configs/config-v34.json @@ -30,7 +30,7 @@ "ConnectionsRateLimitingWindowSeconds": 1, "CrashDBDir": "", "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)", - "DNSSecurityFlags": 1, + "DNSSecurityFlags": 9, "DeadlockDetection": 0, "DeadlockDetectionThreshold": 30, "DisableAPIAuth": false, @@ -43,6 +43,7 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, + "EnableDHTProviders": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, @@ -53,6 +54,7 @@ "EnableMetricReporting": false, "EnableOutgoingNetworkMessageFiltering": true, "EnableP2P": false, + "EnableP2PHybridMode": false, "EnablePingHandler": true, "EnableProcessBlockStats": false, "EnableProfiler": false, @@ -96,6 +98,7 @@ "OptimizeAccountsDatabaseOnStartup": false, "OutgoingMessageFilterBucketCount": 3, "OutgoingMessageFilterBucketSize": 128, + "P2PNetAddress": "", "P2PPersistPeerID": false, "P2PPrivateKeyLocation": "", "ParticipationKeysRefreshInterval": 60000000000, diff --git a/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/genesis.json b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/genesis.json new file mode 100644 index 0000000000..7ae67edf88 --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/genesis.json @@ -0,0 +1,30 @@ +{ + "NetworkName": "hello-p2p", + "VersionModifier": "", + "ConsensusProtocol": "future", + "FirstPartKeyRound": 0, + "LastPartKeyRound": 5000, + "PartKeyDilution": 0, + "Wallets": [ + { + "Name": "Wallet1", + "Stake": 25, + "Online": true + }, + { + "Name": "Wallet2", + "Stake": 25, + "Online": true + }, + { + "Name": "Wallet3", + "Stake": 25, + "Online": true + }, + { + "Name": "Wallet4", + "Stake": 25, + "Online": false + } + ] +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/net.json b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/net.json new file mode 100644 index 0000000000..423d31c1a4 --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/net.json @@ -0,0 +1,107 @@ +{ + "Hosts": [ + { + "Name": "R1", + "Nodes": [ + { + "Name": "relay1", + "IsRelay": true, + "Wallets": [ + { + "Name": "Wallet1", + "ParticipationOnly": false + } + ], + "NetAddress": "{{NetworkPort}}", + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "AdminAPIToken": "{{AdminAPIToken}}", + "EnableTelemetry": true, + "TelemetryURI": "{{TelemetryURI}}", + "EnableMetrics": true, + "MetricsURI": "{{MetricsURI}}", + "EnableService": false, + "EnableBlockStats": true, + "P2PBootstrap": true, + "ConfigJSONOverride": "{ \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableP2P\": true }" + } + ] + }, + { + "Name": "R2", + "Nodes": [ + { + "Name": "relay2", + "IsRelay": true, + "Wallets": [ + { + "Name": "Wallet2", + "ParticipationOnly": false + } + ], + "NetAddress": "{{NetworkPort}}", + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "AdminAPIToken": "{{AdminAPIToken}}", + "EnableTelemetry": true, + "TelemetryURI": "{{TelemetryURI}}", + "EnableMetrics": true, + "MetricsURI": "{{MetricsURI}}", + "EnableService": false, + "EnableBlockStats": true, + "P2PBootstrap": true, + "ConfigJSONOverride": "{ \"DNSBootstrapID\": \".algodev.network\",\"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableP2P\": true }" + } + ] + }, + { + "Name": "N1", + "Group": "", + "Nodes": [ + { + "Name": "node1", + "Wallets": [ + { + "Name": "Wallet3", + "ParticipationOnly": false + } + ], + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "AdminAPIToken": "{{AdminAPIToken}}", + "EnableTelemetry": false, + "TelemetryURI": "{{TelemetryURI}}", + "EnableMetrics": true, + "MetricsURI": "{{MetricsURI}}", + "EnableService": false, + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableP2P\": true }" + } + ] + }, + { + "Name": "NPN1", + "Group": "", + "Nodes": [ + { + "Name": "nonParticipatingNode1", + "Wallets": [ + { + "Name": "Wallet4", + "ParticipationOnly": false + } + ], + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "AdminAPIToken": "{{AdminAPIToken}}", + "EnableTelemetry": false, + "EnableMetrics": true, + "MetricsURI": "{{MetricsURI}}", + "EnableService": false, + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableP2P\": true}" + } + ] + } + ] +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/recipe.json b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/recipe.json new file mode 100644 index 0000000000..a2f88f63b4 --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/recipe.json @@ -0,0 +1,7 @@ +{ + "GenesisFile":"genesis.json", + "NetworkFile":"net.json", + "ConfigFile": "../../configs/reference.json", + "HostTemplatesFile": "../../hosttemplates/hosttemplates.json", + "TopologyFile": "topology.json" +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/topology.json b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/topology.json new file mode 100644 index 0000000000..acc7cca9ec --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-small-p2p/topology.json @@ -0,0 +1,20 @@ +{ + "Hosts": [ + { + "Name": "R1", + "Template": "AWS-US-EAST-1-Small" + }, + { + "Name": "R2", + "Template": "AWS-US-EAST-1-Small" + }, + { + "Name": "N1", + "Template": "AWS-US-EAST-1-Small" + }, + { + "Name": "NPN1", + "Template": "AWS-US-EAST-1-Small" + } + ] +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/genesis.json b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/genesis.json new file mode 100644 index 0000000000..7ae67edf88 --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/genesis.json @@ -0,0 +1,30 @@ +{ + "NetworkName": "hello-p2p", + "VersionModifier": "", + "ConsensusProtocol": "future", + "FirstPartKeyRound": 0, + "LastPartKeyRound": 5000, + "PartKeyDilution": 0, + "Wallets": [ + { + "Name": "Wallet1", + "Stake": 25, + "Online": true + }, + { + "Name": "Wallet2", + "Stake": 25, + "Online": true + }, + { + "Name": "Wallet3", + "Stake": 25, + "Online": true + }, + { + "Name": "Wallet4", + "Stake": 25, + "Online": false + } + ] +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world/hosttemplates.json b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/hosttemplates.json similarity index 100% rename from test/testdata/deployednettemplates/recipes/hello-world/hosttemplates.json rename to test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/hosttemplates.json diff --git a/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/net.json b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/net.json new file mode 100644 index 0000000000..8ea8328c62 --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/net.json @@ -0,0 +1,101 @@ +{ + "Hosts": [ + { + "Name": "R1", + "Nodes": [ + { + "Name": "relay1", + "IsRelay": true, + "Wallets": [ + { + "Name": "Wallet1", + "ParticipationOnly": false + } + ], + "NetAddress": "{{NetworkPort}}", + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "EnableTelemetry": true, + "TelemetryURI": "{{TelemetryURI}}", + "EnableMetrics": true, + "MetricsURI": "{{MetricsURI}}", + "EnableService": false, + "EnableBlockStats": true, + "P2PBootstrap": true, + "ConfigJSONOverride": "{ \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"EnableP2P\": true }" + } + ] + }, + { + "Name": "R2", + "Nodes": [ + { + "Name": "relay2", + "IsRelay": true, + "Wallets": [ + { + "Name": "Wallet2", + "ParticipationOnly": false + } + ], + "NetAddress": "{{NetworkPort}}", + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "EnableTelemetry": true, + "TelemetryURI": "{{TelemetryURI}}", + "EnableMetrics": true, + "MetricsURI": "{{MetricsURI}}", + "EnableService": false, + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"DNSBootstrapID\": \".algodev.network\",\"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"EnableP2P\": true }" + } + ] + }, + { + "Name": "N1", + "Group": "", + "Nodes": [ + { + "Name": "node1", + "Wallets": [ + { + "Name": "Wallet3", + "ParticipationOnly": false + } + ], + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "EnableTelemetry": false, + "TelemetryURI": "{{TelemetryURI}}", + "EnableMetrics": false, + "MetricsURI": "{{MetricsURI}}", + "EnableService": false, + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableP2P\": true }" + } + ] + }, + { + "Name": "NPN1", + "Group": "", + "Nodes": [ + { + "Name": "nonParticipatingNode1", + "Wallets": [ + { + "Name": "Wallet4", + "ParticipationOnly": false + } + ], + "APIEndpoint": "{{APIEndpoint}}", + "APIToken": "{{APIToken}}", + "EnableTelemetry": false, + "EnableMetrics": false, + "EnableService": false, + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableP2P\": true }" + } + ] + } + ] +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/recipe.json b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/recipe.json new file mode 100644 index 0000000000..be6b71ec55 --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/recipe.json @@ -0,0 +1,7 @@ +{ + "GenesisFile":"genesis.json", + "NetworkFile":"net.json", + "ConfigFile": "../../configs/reference.json", + "HostTemplatesFile": "../../hosttemplates/t2micro-useast1.json", + "TopologyFile": "topology.json" +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/topology.json b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/topology.json new file mode 100644 index 0000000000..acc7cca9ec --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/hello-world-tiny-p2p/topology.json @@ -0,0 +1,20 @@ +{ + "Hosts": [ + { + "Name": "R1", + "Template": "AWS-US-EAST-1-Small" + }, + { + "Name": "R2", + "Template": "AWS-US-EAST-1-Small" + }, + { + "Name": "N1", + "Template": "AWS-US-EAST-1-Small" + }, + { + "Name": "NPN1", + "Template": "AWS-US-EAST-1-Small" + } + ] +} diff --git a/test/testdata/deployednettemplates/recipes/hello-world/genesis.json b/test/testdata/deployednettemplates/recipes/hello-world/genesis.json index 218b694d5f..b7fdd9502b 100644 --- a/test/testdata/deployednettemplates/recipes/hello-world/genesis.json +++ b/test/testdata/deployednettemplates/recipes/hello-world/genesis.json @@ -3,7 +3,7 @@ "VersionModifier": "", "ConsensusProtocol": "future", "FirstPartKeyRound": 0, - "LastPartKeyRound": 1000300, + "LastPartKeyRound": 5000, "PartKeyDilution": 0, "Wallets": [ { diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile new file mode 100644 index 0000000000..f4ec4b3c1f --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile @@ -0,0 +1,23 @@ +# scenario1s is scenario1 but smaller, (100 nodes, 100 wallets) -> (20 nodes, 20 wallets), each algod gets single tenancy on a smaller ec2 instance +PARAMS=-w 20 -R 8 -N 20 -n 20 --npn-algod-nodes 10 --node-template node.json --relay-template relay.json --non-participating-node-template nonPartNode.json + +.PHONY: clean all + +all: net.json genesis.json topology.json + +node.json nonPartNode.json relay.json: + python3 copy-node-configs.py + +net.json: node.json nonPartNode.json relay.json ${GOPATH}/bin/netgoal Makefile + netgoal generate -t net -r /tmp/wat -o net.json ${PARAMS} + +genesis.json: ${GOPATH}/bin/netgoal Makefile + netgoal generate -t genesis -r /tmp/wat -o genesis.l.json ${PARAMS} + jq '.LastPartKeyRound=5000|.NetworkName="s1s-p2p"|.ConsensusProtocol="future"' < genesis.l.json > genesis.json + rm genesis.l.json + +topology.json: ../scenario1s/gen_topology.py + python3 ../scenario1s/gen_topology.py + +clean: + rm -f net.json genesis.json node.json nonPartNode.json relay.json topology.json diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md new file mode 100644 index 0000000000..1cad95bc2d --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md @@ -0,0 +1,16 @@ +# Scenario1s for P2P testing + +This is a copy of scenario1s with the following changes in nodes configuration: +1. All nodes get `"EnableP2P": true` into their config. +1. All relays additionally get `"P2PBootstrap": true` to their netgoal config. + +## Build + +```sh +export GOPATH=~/go +make +``` + +## Run + +Run as usual cluster test scenario with algonet. diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py new file mode 100644 index 0000000000..6ffbc01d8d --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py @@ -0,0 +1,55 @@ +""" +Copies node.json, relay.json and nonPartNode.json from scenario1s: +1. Append \"EnableP2P\": true to all configs +2. Set P2PBootstrap: true to relay.json +3. Set DNSSecurityFlags: 0 to all configs +""" + +import json +import os + +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) +SCENARIO1S_DIR = os.path.join(CURRENT_DIR, "..", "scenario1s") + +def main(): + """main""" + with open(os.path.join(SCENARIO1S_DIR, "node.json"), "r") as f: + node = json.load(f) + with open(os.path.join(SCENARIO1S_DIR, "relay.json"), "r") as f: + relay = json.load(f) + with open(os.path.join(SCENARIO1S_DIR, "nonPartNode.json"), "r") as f: + non_part_node = json.load(f) + + # make all relays P2PBootstrap'able + relay["P2PBootstrap"] = True + + # enable P2P for all configs + for config in (node, relay, non_part_node): + override = config.get("ConfigJSONOverride") + if override: + override_json = json.loads(override) + override_json["EnableP2P"] = True + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + config["ConfigJSONOverride"] = json.dumps(override_json) + altconfigs = config.get("AltConfigs", []) + if altconfigs: + for i, altconfig in enumerate(altconfigs): + override = altconfig.get("ConfigJSONOverride") + if override: + override_json = json.loads(override) + override_json["EnableP2P"] = True + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + altconfigs[i]["ConfigJSONOverride"] = json.dumps(override_json) + config["AltConfigs"] = altconfigs + + with open("node.json", "w") as f: + json.dump(node, f, indent=4) + with open("relay.json", "w") as f: + json.dump(relay, f, indent=4) + with open("nonPartNode.json", "w") as f: + json.dump(non_part_node, f, indent=4) + + print("Done!") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/recipe.json b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/recipe.json new file mode 100644 index 0000000000..a2f88f63b4 --- /dev/null +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/recipe.json @@ -0,0 +1,7 @@ +{ + "GenesisFile":"genesis.json", + "NetworkFile":"net.json", + "ConfigFile": "../../configs/reference.json", + "HostTemplatesFile": "../../hosttemplates/hosttemplates.json", + "TopologyFile": "topology.json" +} diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/Makefile b/test/testdata/deployednettemplates/recipes/scenario1s/Makefile index ed8a70132e..8b83c38b6c 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/Makefile +++ b/test/testdata/deployednettemplates/recipes/scenario1s/Makefile @@ -1,14 +1,14 @@ # scenario1s is scenario1 but smaller, (100 nodes, 100 wallets) -> (20 nodes, 20 wallets), each algod gets single tenancy on a smaller ec2 instance PARAMS=-w 20 -R 8 -N 20 -n 20 --npn-algod-nodes 10 --node-template node.json --relay-template relay.json --non-participating-node-template nonPartNode.json -all: net.json genesis.json topology.json bootstrappedFile.json +all: net.json genesis.json topology.json net.json: node.json nonPartNode.json ${GOPATH}/bin/netgoal Makefile netgoal generate -t net -r /tmp/wat -o net.json ${PARAMS} genesis.json: ${GOPATH}/bin/netgoal Makefile netgoal generate -t genesis -r /tmp/wat -o genesis.l.json ${PARAMS} - jq '.LastPartKeyRound=22000|.NetworkName="s1s"|.ConsensusProtocol="future"' < genesis.l.json > genesis.json + jq '.LastPartKeyRound=5000|.NetworkName="s1s"|.ConsensusProtocol="future"' < genesis.l.json > genesis.json rm genesis.l.json topology.json: gen_topology.py diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index cfeb442668..9baf9fafef 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -48,22 +48,33 @@ require ( github.com/elastic/gosigar v0.14.2 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/stdr v1.2.2 // 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/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/google/uuid v1.4.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/ipfs/boxo v0.10.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect + github.com/ipfs/go-datastore v0.6.0 // indirect + github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect + github.com/ipld/go-ipld-prime v0.20.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect + github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.6 // indirect @@ -72,10 +83,14 @@ require ( 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.33.2 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.24.3 // indirect + github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect github.com/libp2p/go-libp2p-pubsub v0.10.0 // indirect + github.com/libp2p/go-libp2p-record v0.2.0 // 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 @@ -104,10 +119,12 @@ require ( github.com/olivere/elastic v6.2.14+incompatible // indirect github.com/onsi/ginkgo/v2 v2.15.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // 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 + github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/common v0.47.0 // indirect @@ -120,6 +137,11 @@ require ( github.com/sirupsen/logrus v1.8.1 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect go.uber.org/dig v1.17.1 // indirect go.uber.org/fx v1.20.1 // indirect go.uber.org/mock v0.4.0 // indirect @@ -133,6 +155,7 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.18.0 // indirect + gonum.org/v1/gonum v0.13.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index dbd7cdeb72..5ed96a265b 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -118,6 +118,7 @@ github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= @@ -131,6 +132,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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= @@ -141,12 +144,16 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev 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= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 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-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/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 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= @@ -162,6 +169,9 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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= @@ -174,7 +184,9 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -185,7 +197,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= @@ -196,12 +210,16 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/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= @@ -209,7 +227,14 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 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/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -221,18 +246,34 @@ github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/C github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/ipfs/boxo v0.10.0 h1:tdDAxq8jrsbRkYoF+5Rcqyeb91hgWe2hp7iLu7ORZLY= +github.com/ipfs/boxo v0.10.0/go.mod h1:Fg+BnfxZ0RPzR0nOodzdIq3A7KgoWAOWsEIImrIQdBM= 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-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= +github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +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-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= +github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= +github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= +github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= 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/ipld/go-ipld-prime v0.20.0 h1:Ud3VwE9ClxpO2LkCYP7vWPc0Fo+dYdYzgxUJZ3uRG4g= +github.com/ipld/go-ipld-prime v0.20.0/go.mod h1:PzqZ/ZR981eKbgdr3y2DJYeD/8bgMawdGVlJDE8kK+M= 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-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 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/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= +github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 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= @@ -240,6 +281,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= @@ -278,14 +320,22 @@ 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.33.2 h1:vCdwnFxoGOXMKmaGHlDSnL4bM3fQeW8pgIa9DECnb40= github.com/libp2p/go-libp2p v0.33.2/go.mod h1:zTeppLuCvUIkT118pFVzA8xzP/p2dJYOMApCkFh0Yww= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= +github.com/libp2p/go-libp2p-kad-dht v0.24.3 h1:VjxtDVWaaf4UFjGBf+yl2JCiGaHx7+ctAUa9oJCR3QE= +github.com/libp2p/go-libp2p-kad-dht v0.24.3/go.mod h1:BShPzRbK6+fN3hk8a0WGAYKpb8m4k+DtchkqouGTrSg= +github.com/libp2p/go-libp2p-kbucket v0.6.3 h1:p507271wWzpy2f1XxPzCQG9NiN6R6lHL9GiSErbQQo0= +github.com/libp2p/go-libp2p-kbucket v0.6.3/go.mod h1:RCseT7AH6eJWxxk2ol03xtP9pEHetYSPXOaJnOiD8i0= github.com/libp2p/go-libp2p-pubsub v0.10.0 h1:wS0S5FlISavMaAbxyQn3dxMOe2eegMfswM471RuHJwA= github.com/libp2p/go-libp2p-pubsub v0.10.0/go.mod h1:1OxbaT/pFRO5h+Dpze8hdHQ63R0ke55XTs6b6NwLLkw= +github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= +github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= @@ -388,6 +438,8 @@ github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8P github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 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= @@ -402,6 +454,8 @@ 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/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= +github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= @@ -423,6 +477,7 @@ github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFD github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= 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/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -460,7 +515,11 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic 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/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 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= @@ -476,6 +535,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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -484,12 +544,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P 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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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/cli v1.22.10/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= @@ -497,6 +561,10 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU 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/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= +github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= +github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= 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= @@ -510,6 +578,15 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec 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.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 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= @@ -522,9 +599,12 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 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/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= @@ -534,6 +614,7 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf 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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -552,6 +633,7 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/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= @@ -578,6 +660,7 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/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= @@ -655,6 +738,9 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -667,6 +753,8 @@ 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= +gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= +gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= 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= @@ -681,6 +769,7 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoA 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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 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= @@ -688,13 +777,18 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -702,6 +796,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= @@ -726,6 +821,7 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh 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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw= diff --git a/tools/debug/algodump/main.go b/tools/debug/algodump/main.go index 4899fb0c73..525ddba705 100644 --- a/tools/debug/algodump/main.go +++ b/tools/debug/algodump/main.go @@ -179,7 +179,11 @@ func main() { *genesisID, protocol.NetworkID(*networkID)) setDumpHandlers(n) - n.Start() + err := n.Start() + if err != nil { + log.Errorf("Failed to start network: %v", err) + return + } for { time.Sleep(time.Second) diff --git a/tools/debug/transplanter/main.go b/tools/debug/transplanter/main.go index 1a41504c99..e6f88d007e 100644 --- a/tools/debug/transplanter/main.go +++ b/tools/debug/transplanter/main.go @@ -393,7 +393,11 @@ func main() { os.Exit(1) } - followerNode.Start() + err = followerNode.Start() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot start follower node: %v", err) + os.Exit(1) + } for followerNode.Ledger().Latest() < basics.Round(*roundStart) { fmt.Printf("At round %d, waiting for %d\n", followerNode.Ledger().Latest(), *roundStart) diff --git a/util/metrics/counter.go b/util/metrics/counter.go index 38852386d8..e9b437a4a2 100644 --- a/util/metrics/counter.go +++ b/util/metrics/counter.go @@ -28,6 +28,14 @@ type Counter struct { // MakeCounter create a new counter with the provided name and description. func MakeCounter(metric MetricName) *Counter { + c := makeCounter(metric) + c.Register(nil) + return c +} + +// makeCounter create a new counter with the provided name and description +// but does not register it with the default registry. +func makeCounter(metric MetricName) *Counter { c := &Counter{c: couge{ values: make([]*cougeValues, 0), description: metric.Description, @@ -35,7 +43,6 @@ func MakeCounter(metric MetricName) *Counter { labels: make(map[string]int), valuesIndices: make(map[int]int), }} - c.Register(nil) return c } diff --git a/util/metrics/gauge.go b/util/metrics/gauge.go index bbc143a14f..edf144e48f 100644 --- a/util/metrics/gauge.go +++ b/util/metrics/gauge.go @@ -27,6 +27,14 @@ type Gauge struct { // MakeGauge create a new gauge with the provided name and description. func MakeGauge(metric MetricName) *Gauge { + c := makeGauge(metric) + c.Register(nil) + return c +} + +// makeGauge create a new gauge with the provided name and description +// but does not register it with the default registry. +func makeGauge(metric MetricName) *Gauge { c := &Gauge{g: couge{ values: make([]*cougeValues, 0), description: metric.Description, @@ -34,7 +42,6 @@ func MakeGauge(metric MetricName) *Gauge { labels: make(map[string]int), valuesIndices: make(map[int]int), }} - c.Register(nil) return c } diff --git a/util/metrics/opencensus.go b/util/metrics/opencensus.go new file mode 100644 index 0000000000..fefb1d054b --- /dev/null +++ b/util/metrics/opencensus.go @@ -0,0 +1,172 @@ +// Copyright (C) 2019-2024 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 . + +// Functions for opencensus stats aggs conversion to our internal data type +// suitable for further reporting + +package metrics + +import ( + "context" + "strings" + + "go.opencensus.io/metric/metricdata" + "go.opencensus.io/metric/metricexport" + "golang.org/x/exp/slices" +) + +type defaultOpencensusGatherer struct { + names []string +} + +// WriteMetric return opencensus data converted to algorand format +func (og *defaultOpencensusGatherer) WriteMetric(buf *strings.Builder, parentLabels string) { + metrics := collectOpenCensusMetrics(og.names) + for _, metric := range metrics { + metric.WriteMetric(buf, parentLabels) + } +} + +// AddMetric return opencensus data converted to algorand format +func (og *defaultOpencensusGatherer) AddMetric(values map[string]float64) { + metrics := collectOpenCensusMetrics(og.names) + for _, metric := range metrics { + metric.AddMetric(values) + } +} + +type statExporter struct { + names map[string]struct{} + metrics []Metric +} + +func collectOpenCensusMetrics(names []string) []Metric { + exporter := &statExporter{} + if len(names) > 0 { + exporter.names = make(map[string]struct{}, len(names)) + for _, name := range names { + exporter.names[name] = struct{}{} + } + } + reader := metricexport.NewReader() + reader.ReadAndExport(exporter) + + return exporter.metrics +} + +// statCounter stores single int64 value per stat with labels +type statCounter struct { + name string + description string + labels []map[string]string + values []int64 +} + +// WriteMetric outputs Prometheus metrics for all labels/values in statCounter +func (st *statCounter) WriteMetric(buf *strings.Builder, parentLabels string) { + name := sanitizePrometheusName(st.name) + counter := makeCounter(MetricName{name, st.description}) + for i := 0; i < len(st.labels); i++ { + counter.AddUint64(uint64(st.values[i]), st.labels[i]) + } + counter.WriteMetric(buf, parentLabels) +} + +// AddMetric outputs all statCounter's labels/values into a map +func (st *statCounter) AddMetric(values map[string]float64) { + counter := makeCounter(MetricName{st.name, st.description}) + for i := 0; i < len(st.labels); i++ { + counter.AddUint64(uint64(st.values[i]), st.labels[i]) + } + counter.AddMetric(values) +} + +// statCounter stores single float64 sun value per stat with labels +type statDistribution struct { + name string + description string + labels []map[string]string + values []float64 +} + +// WriteMetric outputs Prometheus metrics for all labels/values in statCounter +func (st *statDistribution) WriteMetric(buf *strings.Builder, parentLabels string) { + name := sanitizePrometheusName(st.name) + gauge := makeGauge(MetricName{name, st.description}) + for i := 0; i < len(st.labels); i++ { + gauge.SetLabels(uint64(st.values[i]), st.labels[i]) + } + gauge.WriteMetric(buf, parentLabels) +} + +// AddMetric outputs all statCounter's labels/values into a map +func (st *statDistribution) AddMetric(values map[string]float64) { + gauge := makeGauge(MetricName{st.name, st.description}) + for i := 0; i < len(st.labels); i++ { + gauge.SetLabels(uint64(st.values[i]), st.labels[i]) + } + gauge.AddMetric(values) +} + +func (s *statExporter) ExportMetrics(ctx context.Context, data []*metricdata.Metric) error { + labeler := func(lk []metricdata.LabelKey, lv []metricdata.LabelValue, ignores ...string) map[string]string { + // default labeler concatenates labels + labels := make(map[string]string, len(lk)) + for i := range lk { + if lv[i].Present && (len(ignores) == 0 || len(ignores) > 0 && !slices.Contains(ignores, lk[i].Key)) { + labels[lk[i].Key] = lv[i].Value + } + } + return labels + } + + for _, m := range data { + if _, ok := s.names[m.Descriptor.Name]; len(s.names) > 0 && !ok { + continue + } + if m.Descriptor.Type == metricdata.TypeCumulativeInt64 { + counter := statCounter{ + name: m.Descriptor.Name, + description: m.Descriptor.Description, + } + for _, d := range m.TimeSeries { + // ignore a known useless instance_id label + labels := labeler(m.Descriptor.LabelKeys, d.LabelValues, "instance_id") + counter.labels = append(counter.labels, labels) + counter.values = append(counter.values, d.Points[0].Value.(int64)) + } + + s.metrics = append(s.metrics, &counter) + } else if m.Descriptor.Type == metricdata.TypeCumulativeDistribution { + // TODO: the metrics below cannot be integer gauge, and Sum statistic does not make any sense. + // libp2p.io/dht/kad/outbound_request_latency + // libp2p.io/dht/kad/inbound_request_latency + // Ignore? + dist := statDistribution{ + name: m.Descriptor.Name, + description: m.Descriptor.Description, + } + // check if we are processing a known DHT metric + for _, d := range m.TimeSeries { + label := labeler(m.Descriptor.LabelKeys, d.LabelValues, "instance_id") + dist.labels = append(dist.labels, label) + dist.values = append(dist.values, d.Points[0].Value.(*metricdata.Distribution).Sum) + } + s.metrics = append(s.metrics, &dist) + } + } + return nil +} diff --git a/util/metrics/opencensus_test.go b/util/metrics/opencensus_test.go new file mode 100644 index 0000000000..f5401af541 --- /dev/null +++ b/util/metrics/opencensus_test.go @@ -0,0 +1,147 @@ +// Copyright (C) 2019-2024 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 metrics + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +// TestDHTOpenCensusMetrics ensures both count and distribution stats are properly converted to our metrics +func TestDHTOpenCensusMetrics(t *testing.T) { + partitiontest.PartitionTest(t) + + defaultBytesDistribution := view.Distribution(1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296) + + keyMessageType := tag.MustNewKey("message_type") + keyPeerID := tag.MustNewKey("peer_id") + keyInstanceID := tag.MustNewKey("instance_id") + + sentMessages := stats.Int64("my_sent_messages", "Total number of messages sent per RPC", stats.UnitDimensionless) + receivedBytes := stats.Int64("my_received_bytes", "Total received bytes per RPC", stats.UnitBytes) + + receivedBytesView := &view.View{ + Measure: receivedBytes, + TagKeys: []tag.Key{keyMessageType, keyPeerID, keyInstanceID}, + Aggregation: defaultBytesDistribution, + } + sentMessagesView := &view.View{ + Measure: sentMessages, + TagKeys: []tag.Key{keyMessageType, keyPeerID, keyInstanceID}, + Aggregation: view.Count(), + } + + err := view.Register(receivedBytesView, sentMessagesView) + require.NoError(t, err) + defer view.Unregister(receivedBytesView, sentMessagesView) + + ctx := context.Background() + tags1 := []tag.Mutator{ + tag.Upsert(keyMessageType, "UNKNOWN"), + tag.Upsert(keyPeerID, "1234"), + tag.Upsert(keyInstanceID, fmt.Sprintf("%p", t)), + } + ctx1, _ := tag.New(ctx, tags1...) + + stats.Record(ctx1, + sentMessages.M(1), + receivedBytes.M(int64(100)), + ) + + tags2 := []tag.Mutator{ + tag.Upsert(keyMessageType, "ADD_PROVIDER"), + tag.Upsert(keyPeerID, "abcd"), + tag.Upsert(keyInstanceID, fmt.Sprintf("%p", t)), + } + ctx2, _ := tag.New(ctx, tags2...) + + stats.Record(ctx2, + sentMessages.M(1), + receivedBytes.M(int64(123)), + ) + + // first check some metrics are collected when no names provided + // cannot assert on specific values because network tests might run in parallel with this package + // and produce some metric under specific configuration + require.Eventually(t, func() bool { + // stats are written by a background goroutine, give it a chance to finish + metrics := collectOpenCensusMetrics(nil) + return len(metrics) >= 2 + }, 10*time.Second, 20*time.Millisecond) + + // now assert on specific names and values + metrics := collectOpenCensusMetrics([]string{"my_sent_messages", "my_received_bytes"}) + require.Len(t, metrics, 2) + for _, m := range metrics { + var buf strings.Builder + m.WriteMetric(&buf, "") + promValue := buf.String() + if strings.Contains(promValue, "my_sent_messages") { + require.Contains(t, promValue, "my_sent_messages counter\n") + require.Contains(t, promValue, `peer_id="abcd"`) + require.Contains(t, promValue, `peer_id="1234"`) + require.Contains(t, promValue, `message_type="ADD_PROVIDER"`) + require.Contains(t, promValue, `message_type="UNKNOWN"`) + require.Contains(t, promValue, "} 1\n") + } else if strings.Contains(promValue, "my_received_bytes") { + require.Contains(t, promValue, "my_received_bytes gauge\n") + require.Contains(t, promValue, `peer_id="1234"`) + require.Contains(t, promValue, `peer_id="abcd"`) + require.Contains(t, promValue, `message_type="ADD_PROVIDER"`) + require.Contains(t, promValue, `message_type="UNKNOWN"`) + require.Contains(t, promValue, "} 123\n") + require.Contains(t, promValue, "} 100\n") + } else { + require.Fail(t, "not expected metric", promValue) + } + + values := make(map[string]float64) + m.AddMetric(values) + for k, v := range values { + require.True(t, strings.Contains(k, "message_type__ADD_PROVIDER") || strings.Contains(k, "message_type__UNKNOWN")) + require.True(t, strings.Contains(k, "peer_id__1234") || strings.Contains(k, "peer_id__abcd")) + if strings.Contains(k, "my_sent_messages") { + require.Equal(t, v, float64(1)) + } else if strings.Contains(k, "my_received_bytes") { + require.True(t, v == 100 || v == 123) + } else { + require.Fail(t, "not expected metric key", k) + } + } + } + + // ensure the exported gatherer works + reg := MakeRegistry() + reg.Register(&OpencensusDefaultMetrics) + defer reg.Deregister(&OpencensusDefaultMetrics) + + var buf strings.Builder + reg.WriteMetrics(&buf, "") + + require.Contains(t, buf.String(), "my_sent_messages") + require.Contains(t, buf.String(), "my_received_bytes") +} diff --git a/util/metrics/prometheus.go b/util/metrics/prometheus.go new file mode 100644 index 0000000000..b55f931001 --- /dev/null +++ b/util/metrics/prometheus.go @@ -0,0 +1,106 @@ +// Copyright (C) 2019-2024 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 . + +// Functions for Prometheus metrics conversion to our internal data type +// suitable for further reporting + +package metrics + +import ( + "strings" + + "github.com/prometheus/client_golang/prometheus" + iopc "github.com/prometheus/client_model/go" +) + +type defaultPrometheusGatherer struct { + names []string +} + +// WriteMetric return prometheus converted to algorand format. +// Supports only counter and gauge types and ignores go_ metrics. +func (pg *defaultPrometheusGatherer) WriteMetric(buf *strings.Builder, parentLabels string) { + metrics := collectPrometheusMetrics(pg.names) + for _, metric := range metrics { + metric.WriteMetric(buf, parentLabels) + } +} + +// AddMetric return prometheus data converted to algorand format. +// Supports only counter and gauge types and ignores go_ metrics. +func (pg *defaultPrometheusGatherer) AddMetric(values map[string]float64) { + metrics := collectPrometheusMetrics(pg.names) + for _, metric := range metrics { + metric.AddMetric(values) + } +} + +func collectPrometheusMetrics(names []string) []Metric { + var result []Metric + var namesMap map[string]struct{} + if len(names) > 0 { + namesMap = make(map[string]struct{}, len(names)) + for _, name := range names { + namesMap[name] = struct{}{} + } + } + + convertLabels := func(m *iopc.Metric) map[string]string { + var labels map[string]string + if lbls := m.GetLabel(); len(lbls) > 0 { + labels = make(map[string]string, len(lbls)) + for _, lbl := range lbls { + labels[lbl.GetName()] = lbl.GetValue() + } + } + return labels + } + metrics, _ := prometheus.DefaultGatherer.Gather() + for _, metric := range metrics { + if strings.HasPrefix(metric.GetName(), "go_") { + continue + } + if _, ok := namesMap[metric.GetName()]; len(namesMap) > 0 && ok || len(namesMap) == 0 { + if metric.GetType() == iopc.MetricType_COUNTER && metric.GetMetric() != nil { + counter := makeCounter(MetricName{metric.GetName(), metric.GetHelp()}) + ma := metric.GetMetric() + for _, m := range ma { + if m.GetCounter() == nil { + continue + } + val := uint64(m.GetCounter().GetValue()) + labels := convertLabels(m) + counter.AddUint64(val, labels) + } + result = append(result, counter) + } else if metric.GetType() == iopc.MetricType_GAUGE && metric.GetMetric() != nil { + gauge := makeGauge(MetricName{metric.GetName(), metric.GetHelp()}) + + ma := metric.GetMetric() + for _, m := range ma { + if m.GetGauge() == nil { + continue + } + val := uint64(m.GetGauge().GetValue()) + labels := convertLabels(m) + gauge.SetLabels(val, labels) + } + result = append(result, gauge) + } + } + } + return result +} diff --git a/util/metrics/prometheus_test.go b/util/metrics/prometheus_test.go new file mode 100644 index 0000000000..75ef94f97e --- /dev/null +++ b/util/metrics/prometheus_test.go @@ -0,0 +1,148 @@ +// Copyright (C) 2019-2024 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 . + +// Functions for Prometheus metrics conversion to our internal data type +// suitable for further reporting + +package metrics + +import ( + "strings" + "testing" + "time" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" +) + +func TestPrometheusMetrics(t *testing.T) { + partitiontest.PartitionTest(t) + + const metricNamespace = "test_metric" + + // gauge vec with labels + gaugeLabels := prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: metricNamespace, + Name: "streams", + Help: "Number of Streams", + }, []string{"dir", "scope", "protocol"}) + + // gauge without labels + gauge := prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: metricNamespace, + Name: "protocols_count", + Help: "Protocols Count", + }, + ) + + // counter with labels + counterLabels := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Name: "identify_total", + Help: "Identify", + }, + []string{"dir"}, + ) + + // counter without labels + counter := prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: metricNamespace, + Name: "counter_total", + Help: "Counter", + }, + ) + + prometheus.DefaultRegisterer.MustRegister(gaugeLabels) + prometheus.DefaultRegisterer.MustRegister(gauge) + prometheus.DefaultRegisterer.MustRegister(counterLabels) + prometheus.DefaultRegisterer.MustRegister(counter) + + defer prometheus.DefaultRegisterer.Unregister(gaugeLabels) + defer prometheus.DefaultRegisterer.Unregister(gauge) + defer prometheus.DefaultRegisterer.Unregister(counterLabels) + defer prometheus.DefaultRegisterer.Unregister(counter) + + // set some values + tags := []string{"outbound", "protocol", "/test/proto"} + gaugeLabels.WithLabelValues(tags...).Set(float64(1)) + + gauge.Set(float64(2)) + + tags = []string{"inbound"} + counterLabels.WithLabelValues(tags...).Add(float64(3)) + + counter.Add(float64(4)) + + // wait they collected and ready for gathering + require.Eventually(t, func() bool { + metrics := collectPrometheusMetrics(nil) + return len(metrics) >= 4 + }, 5*time.Second, 100*time.Millisecond) + + metrics := collectPrometheusMetrics([]string{ + metricNamespace + "_streams", + metricNamespace + "_protocols_count", + metricNamespace + "_identify_total", + metricNamespace + "_counter_total"}) + require.Len(t, metrics, 4) + + for _, m := range metrics { + buf := strings.Builder{} + m.WriteMetric(&buf, "") + promValue := buf.String() + if strings.Contains(promValue, metricNamespace+"_streams") { + require.Contains(t, promValue, metricNamespace+"_streams gauge\n") + require.Contains(t, promValue, metricNamespace+"_streams{") + // map/labels order is not guaranteed + require.Contains(t, promValue, "dir=\"outbound\"") + require.Contains(t, promValue, "protocol=\"/test/proto\"") + require.Contains(t, promValue, "scope=\"protocol\"") + require.Contains(t, promValue, "} 1\n") + } else if strings.Contains(promValue, metricNamespace+"_protocols_count") { + require.Contains(t, promValue, metricNamespace+"_protocols_count gauge\n") + require.Contains(t, promValue, metricNamespace+"_protocols_count 2\n") + } else if strings.Contains(promValue, metricNamespace+"_identify_total") { + require.Contains(t, promValue, metricNamespace+"_identify_total counter\n") + require.Contains(t, promValue, metricNamespace+"_identify_total{dir=\"inbound\"} 3\n") + } else if strings.Contains(promValue, metricNamespace+"_counter_total") { + require.Contains(t, promValue, metricNamespace+"_counter_total counter\n") + require.Contains(t, promValue, metricNamespace+"_counter_total 4\n") + } else { + require.Fail(t, "not expected metric", promValue) + } + + values := make(map[string]float64) + m.AddMetric(values) + require.Len(t, values, 1) + } + + // ensure the exported gatherer works + reg := MakeRegistry() + reg.Register(&PrometheusDefaultMetrics) + defer reg.Deregister(&PrometheusDefaultMetrics) + + var buf strings.Builder + reg.WriteMetrics(&buf, "") + + require.Contains(t, buf.String(), metricNamespace+"_streams") + require.Contains(t, buf.String(), metricNamespace+"_protocols_count") + require.Contains(t, buf.String(), metricNamespace+"_identify_total") + require.Contains(t, buf.String(), metricNamespace+"_counter_total") +} diff --git a/util/metrics/registry.go b/util/metrics/registry.go index 43078bb4c2..d525bc1833 100644 --- a/util/metrics/registry.go +++ b/util/metrics/registry.go @@ -37,6 +37,12 @@ func DefaultRegistry() *Registry { return defaultRegistry } +// PrometheusDefaultMetrics is the default prometheus gatherer implementing the Metric interface +var PrometheusDefaultMetrics = defaultPrometheusGatherer{} + +// OpencensusDefaultMetrics is the default prometheus gatherer implementing the Metric interface +var OpencensusDefaultMetrics = defaultOpencensusGatherer{} + func init() { defaultRegistry = MakeRegistry() } From 4ba009b3d60be88377d9661ce6eb96d8fa1fdeed Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:32:54 -0400 Subject: [PATCH 163/218] network: use http.ResponseController instead of GetHTTPRequestConnection (#6044) --- agreement/gossip/network_test.go | 3 - components/mocks/mockNetwork.go | 5 -- network/gossipNode.go | 12 ---- network/hybridNetwork.go | 10 --- network/p2p/p2p.go | 50 +------------ network/p2p/p2p_test.go | 66 ----------------- network/p2p/streams.go | 20 ------ network/p2pNetwork.go | 18 ----- network/p2pNetwork_test.go | 9 +-- network/requestTracker.go | 120 +++---------------------------- network/requestTracker_test.go | 4 +- network/wsNetwork.go | 11 --- rpcs/ledgerService.go | 19 +++-- 13 files changed, 25 insertions(+), 322 deletions(-) diff --git a/agreement/gossip/network_test.go b/agreement/gossip/network_test.go index a3c5328716..2867734ff6 100644 --- a/agreement/gossip/network_test.go +++ b/agreement/gossip/network_test.go @@ -155,9 +155,6 @@ func (w *whiteholeNetwork) GetPeers(options ...network.PeerOption) []network.Pee } func (w *whiteholeNetwork) RegisterHTTPHandler(path string, handler http.Handler) { } -func (w *whiteholeNetwork) GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettableConn) { - return nil -} func (w *whiteholeNetwork) Start() error { w.quit = make(chan struct{}) diff --git a/components/mocks/mockNetwork.go b/components/mocks/mockNetwork.go index f933a553a9..47b1a5b5e4 100644 --- a/components/mocks/mockNetwork.go +++ b/components/mocks/mockNetwork.go @@ -106,11 +106,6 @@ func (network *MockNetwork) RegisterHTTPHandler(path string, handler http.Handle // OnNetworkAdvance - empty implementation func (network *MockNetwork) OnNetworkAdvance() {} -// GetHTTPRequestConnection - empty implementation -func (network *MockNetwork) GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettableConn) { - return nil -} - // GetGenesisID - empty implementation func (network *MockNetwork) GetGenesisID() string { if network.GenesisID == "" { diff --git a/network/gossipNode.go b/network/gossipNode.go index 6a028ff193..1592641f70 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -20,7 +20,6 @@ import ( "context" "net/http" "strings" - "time" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/protocol" @@ -50,13 +49,6 @@ const ( PeersPhonebookArchivalNodes PeerOption = iota ) -// DeadlineSettableConn abstracts net.Conn and related types as deadline-settable -type DeadlineSettableConn interface { - SetDeadline(time.Time) error - SetReadDeadline(time.Time) error - SetWriteDeadline(time.Time) error -} - // GossipNode represents a node in the gossip network type GossipNode interface { Address() (string, bool) @@ -104,10 +96,6 @@ type GossipNode interface { // characteristics as with a watchdog timer. OnNetworkAdvance() - // GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same - // request that was provided to the http handler ( or provide a fallback Context() to that ) - GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) - // GetGenesisID returns the network-specific genesisID. GetGenesisID() string diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go index f324deb73f..27fc6edbb0 100644 --- a/network/hybridNetwork.go +++ b/network/hybridNetwork.go @@ -212,16 +212,6 @@ func (n *HybridP2PNetwork) OnNetworkAdvance() { }) } -// GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same -// request that was provided to the http handler ( or provide a fallback Context() to that ) -func (n *HybridP2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) { - conn = n.wsNetwork.GetHTTPRequestConnection(request) - if conn != nil { - return conn - } - return n.p2pNetwork.GetHTTPRequestConnection(request) -} - // GetGenesisID returns the network-specific genesisID. func (n *HybridP2PNetwork) GetGenesisID() string { return n.genesisID diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index f4ed670f3e..ac0489d5e1 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -38,7 +38,6 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" - libp2phttp "github.com/libp2p/go-libp2p/p2p/http" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" "github.com/libp2p/go-libp2p/p2p/security/noise" "github.com/libp2p/go-libp2p/p2p/transport/tcp" @@ -67,8 +66,6 @@ type Service interface { ListPeersForTopic(topic string) []peer.ID Subscribe(topic string, val pubsub.ValidatorEx) (SubNextCancellable, error) Publish(ctx context.Context, topic string, data []byte) error - - GetStream(peer.ID) (network.Stream, bool) } // serviceImpl manages integration with libp2p and implements the Service interface @@ -137,47 +134,7 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. libp2p.Security(noise.ID, noise.New), disableMetrics, ) - return &StreamChainingHost{ - Host: host, - handlers: map[protocol.ID][]network.StreamHandler{}, - }, listenAddr, err -} - -// StreamChainingHost is a wrapper around host.Host that overrides SetStreamHandler -// to allow chaining multiple handlers for the same protocol. -// Note, there should be probably only single handler that writes/reads streams. -type StreamChainingHost struct { - host.Host - handlers map[protocol.ID][]network.StreamHandler - mutex deadlock.Mutex -} - -// SetStreamHandler overrides the host.Host.SetStreamHandler method for chaining multiple handlers. -// Function objects are not comparable so theoretically it could have duplicates. -// The main use case is to track HTTP streams for ProtocolIDForMultistreamSelect = "/http/1.1" -// so it could just filter for such protocol if there any issues with other protocols like kad or mesh. -func (h *StreamChainingHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) { - h.mutex.Lock() - defer h.mutex.Unlock() - - handlers := h.handlers[pid] - if len(handlers) == 0 { - // no other handlers, do not set a proxy handler - h.Host.SetStreamHandler(pid, handler) - h.handlers[pid] = append(handlers, handler) - return - } - // otherwise chain the handlers with a copy of the existing handlers - handlers = append(handlers, handler) - // copy to save it in the closure and call lock free - currentHandlers := make([]network.StreamHandler, len(handlers)) - copy(currentHandlers, handlers) - h.Host.SetStreamHandler(pid, func(s network.Stream) { - for _, h := range currentHandlers { - h(s) - } - }) - h.handlers[pid] = handlers + return host, listenAddr, err } // MakeService creates a P2P service instance @@ -186,7 +143,6 @@ func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h ho sm := makeStreamManager(ctx, log, h, wsStreamHandler) h.Network().Notify(sm) h.SetStreamHandler(AlgorandWsProtocol, sm.streamHandler) - h.SetStreamHandler(libp2phttp.ProtocolIDForMultistreamSelect, sm.streamHandlerHTTP) // set an empty handler for telemetryID/telemetryInstance protocol in order to allow other peers to know our telemetryID telemetryID := log.GetTelemetryGUID() @@ -294,10 +250,6 @@ func (s *serviceImpl) ClosePeer(peer peer.ID) error { return s.host.Network().ClosePeer(peer) } -func (s *serviceImpl) GetStream(peerID peer.ID) (network.Stream, bool) { - return s.streams.getStream(peerID) -} - // netAddressToListenAddress converts a netAddress in "ip:port" format to a listen address // that can be passed in to libp2p.ListenAddrStrings func netAddressToListenAddress(netAddress string) (string, error) { diff --git a/network/p2p/p2p_test.go b/network/p2p/p2p_test.go index fb14193a55..dab6aa5456 100644 --- a/network/p2p/p2p_test.go +++ b/network/p2p/p2p_test.go @@ -19,19 +19,14 @@ package p2p import ( "context" "fmt" - "sync/atomic" "testing" - "time" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" - "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/network/p2p/peerstore" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -86,67 +81,6 @@ func TestNetAddressToListenAddress(t *testing.T) { } } -func TestP2PStreamingHost(t *testing.T) { - partitiontest.PartitionTest(t) - - cfg := config.GetDefaultLocal() - dir := t.TempDir() - pstore, err := peerstore.NewPeerStore(nil, "") - require.NoError(t, err) - h, la, err := MakeHost(cfg, dir, pstore) - require.NoError(t, err) - - var h1calls atomic.Int64 - h1 := func(network.Stream) { - h1calls.Add(1) - } - var h2calls atomic.Int64 - h2 := func(network.Stream) { - h2calls.Add(1) - } - - ma, err := multiaddr.NewMultiaddr(la) - require.NoError(t, err) - h.Network().Listen(ma) - defer h.Close() - - h.SetStreamHandler(AlgorandWsProtocol, h1) - h.SetStreamHandler(AlgorandWsProtocol, h2) - - addrInfo := peer.AddrInfo{ - ID: h.ID(), - Addrs: h.Addrs(), - } - cpstore, err := peerstore.NewPeerStore([]*peer.AddrInfo{&addrInfo}, "") - require.NoError(t, err) - c, _, err := MakeHost(cfg, dir, cpstore) - require.NoError(t, err) - defer c.Close() - - s1, err := c.NewStream(context.Background(), h.ID(), AlgorandWsProtocol) - require.NoError(t, err) - s1.Write([]byte("hello")) - defer s1.Close() - - require.Eventually(t, func() bool { - return h1calls.Load() == 1 && h2calls.Load() == 1 - }, 5*time.Second, 100*time.Millisecond) - - // ensure a single handler also works as expected - h1calls.Store(0) - h.SetStreamHandler(algorandP2pHTTPProtocol, h1) - - s2, err := c.NewStream(context.Background(), h.ID(), algorandP2pHTTPProtocol) - require.NoError(t, err) - s2.Write([]byte("hello")) - defer s2.Close() - - require.Eventually(t, func() bool { - return h1calls.Load() == 1 - }, 5*time.Second, 100*time.Millisecond) - -} - // TestP2PGetPeerTelemetryInfo tests the GetPeerTelemetryInfo function func TestP2PGetPeerTelemetryInfo(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/network/p2p/streams.go b/network/p2p/streams.go index d16633adfd..e7277f4871 100644 --- a/network/p2p/streams.go +++ b/network/p2p/streams.go @@ -104,20 +104,6 @@ func (n *streamManager) streamHandler(stream network.Stream) { n.handler(n.ctx, remotePeer, stream, incoming) } -// streamHandlerHTTP tracks the ProtocolIDForMultistreamSelect = "/http/1.1" streams -func (n *streamManager) streamHandlerHTTP(stream network.Stream) { - n.streamsLock.Lock() - defer n.streamsLock.Unlock() - n.streams[stream.Conn().LocalPeer()] = stream -} - -func (n *streamManager) getStream(peerID peer.ID) (network.Stream, bool) { - n.streamsLock.Lock() - defer n.streamsLock.Unlock() - stream, ok := n.streams[peerID] - return stream, ok -} - // Connected is called when a connection is opened func (n *streamManager) Connected(net network.Network, conn network.Conn) { remotePeer := conn.RemotePeer() @@ -174,12 +160,6 @@ func (n *streamManager) Disconnected(net network.Network, conn network.Conn) { stream.Close() delete(n.streams, conn.RemotePeer()) } - - stream, ok = n.streams[conn.LocalPeer()] - if ok { - stream.Close() - delete(n.streams, conn.LocalPeer()) - } } // Listen is called when network starts listening on an addr diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 7ebbb5a665..1ad49bd045 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -19,7 +19,6 @@ package network import ( "context" "math/rand" - "net" "net/http" "strings" "sync" @@ -725,23 +724,6 @@ func (n *P2PNetwork) OnNetworkAdvance() { } } -// GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same -// request that was provided to the http handler ( or provide a fallback Context() to that ) -func (n *P2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) { - addr := request.Context().Value(http.LocalAddrContextKey).(net.Addr) - peerID, err := peer.Decode(addr.String()) - if err != nil { - n.log.Infof("GetHTTPRequestConnection failed to decode %s", addr.String()) - return nil - } - conn, ok := n.service.GetStream(peerID) - if !ok { - n.log.Warnf("GetHTTPRequestConnection no such stream for peer %s", peerID.String()) - return nil - } - return conn -} - // wsStreamHandler is a callback that the p2p package calls when a new peer connects and establishes a // stream for the websocket protocol. func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) { diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 5bd582ead0..3548dbd1cb 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -366,10 +366,6 @@ func (s *mockService) Publish(ctx context.Context, topic string, data []byte) er return nil } -func (s *mockService) GetStream(peer.ID) (network.Stream, bool) { - return nil, false -} - func makeMockService(id peer.ID, addrs []ma.Multiaddr) *mockService { return &mockService{ id: id, @@ -725,8 +721,9 @@ type p2phttpHandler struct { func (h *p2phttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte(h.retData)) if r.URL.Path == "/check-conn" { - c := h.net.GetHTTPRequestConnection(r) - require.NotNil(h.tb, c) + rc := http.NewResponseController(w) + err := rc.SetWriteDeadline(time.Now().Add(10 * time.Second)) + require.NoError(h.tb, err) } } diff --git a/network/requestTracker.go b/network/requestTracker.go index 47eba90c7e..8c603e2a32 100644 --- a/network/requestTracker.go +++ b/network/requestTracker.go @@ -62,12 +62,10 @@ type TrackerRequest struct { otherTelemetryGUID string otherInstanceName string - connection net.Conn - noPrune bool } // makeTrackerRequest creates a new TrackerRequest. -func makeTrackerRequest(remoteAddr, remoteHost, remotePort string, createTime time.Time, conn net.Conn) *TrackerRequest { +func makeTrackerRequest(remoteAddr, remoteHost, remotePort string, createTime time.Time) *TrackerRequest { if remoteHost == "" { remoteHost, remotePort, _ = net.SplitHostPort(remoteAddr) } @@ -77,7 +75,6 @@ func makeTrackerRequest(remoteAddr, remoteHost, remotePort string, createTime ti remoteAddr: remoteAddr, remoteHost: remoteHost, remotePort: remotePort, - connection: conn, } } @@ -120,9 +117,8 @@ func (tr *TrackerRequest) remoteAddress() string { // hostIncomingRequests holds all the requests that are originating from a single host. type hostIncomingRequests struct { - remoteHost string - requests []*TrackerRequest // this is an ordered list, according to the requestsHistory.created - additionalHostRequests map[*TrackerRequest]struct{} // additional requests that aren't included in the "requests", and always assumed to be "alive". + remoteHost string + requests []*TrackerRequest // this is an ordered list, according to the requestsHistory.created } // findTimestampIndex finds the first an index (i) in the sorted requests array, where requests[i].created is greater than t. @@ -137,45 +133,6 @@ func (ard *hostIncomingRequests) findTimestampIndex(t time.Time) int { return i } -// convertToAdditionalRequest converts the given trackerRequest into a "additional request". -// unlike regular tracker requests, additional requests does not get pruned. -func (ard *hostIncomingRequests) convertToAdditionalRequest(trackerRequest *TrackerRequest) { - if _, has := ard.additionalHostRequests[trackerRequest]; has { - return - } - - i := sort.Search(len(ard.requests), func(i int) bool { - return ard.requests[i].created.After(trackerRequest.created) - }) - i-- - if i < 0 { - return - } - // we could have several entries with the same timestamp, so we need to consider all of them. - for ; i >= 0; i-- { - if ard.requests[i] == trackerRequest { - break - } - if ard.requests[i].created != trackerRequest.created { - // we can't find the item in the list. - return - } - } - if i < 0 { - return - } - // ok, item was found at index i. - copy(ard.requests[i:], ard.requests[i+1:]) - ard.requests[len(ard.requests)-1] = nil - ard.requests = ard.requests[:len(ard.requests)-1] - ard.additionalHostRequests[trackerRequest] = struct{}{} -} - -// removeTrackedConnection removes a trackerRequest from the additional requests map -func (ard *hostIncomingRequests) removeTrackedConnection(trackerRequest *TrackerRequest) { - delete(ard.additionalHostRequests, trackerRequest) -} - // add adds the trackerRequest at the correct index within the sorted array. func (ard *hostIncomingRequests) add(trackerRequest *TrackerRequest) { // find the new item index. @@ -197,7 +154,7 @@ func (ard *hostIncomingRequests) add(trackerRequest *TrackerRequest) { // countConnections counts the number of connection that we have that occurred after the provided specified time func (ard *hostIncomingRequests) countConnections(rateLimitingWindowStartTime time.Time) (count uint) { i := ard.findTimestampIndex(rateLimitingWindowStartTime) - return uint(len(ard.requests) - i + len(ard.additionalHostRequests)) + return uint(len(ard.requests) - i) } //msgp:ignore hostsIncomingMap @@ -232,9 +189,8 @@ func (him *hostsIncomingMap) addRequest(trackerRequest *TrackerRequest) { requestData, has := (*him)[trackerRequest.remoteHost] if !has { requestData = &hostIncomingRequests{ - remoteHost: trackerRequest.remoteHost, - requests: make([]*TrackerRequest, 0, 1), - additionalHostRequests: make(map[*TrackerRequest]struct{}), + remoteHost: trackerRequest.remoteHost, + requests: make([]*TrackerRequest, 0, 1), } (*him)[trackerRequest.remoteHost] = requestData } @@ -250,24 +206,6 @@ func (him *hostsIncomingMap) countOriginConnections(remoteHost string, rateLimit return 0 } -// convertToAdditionalRequest converts the given trackerRequest into a "additional request". -func (him *hostsIncomingMap) convertToAdditionalRequest(trackerRequest *TrackerRequest) { - requestData, has := (*him)[trackerRequest.remoteHost] - if !has { - return - } - requestData.convertToAdditionalRequest(trackerRequest) -} - -// removeTrackedConnection removes a trackerRequest from the additional requests map -func (him *hostsIncomingMap) removeTrackedConnection(trackerRequest *TrackerRequest) { - requestData, has := (*him)[trackerRequest.remoteHost] - if !has { - return - } - requestData.removeTrackedConnection(trackerRequest) -} - // RequestTracker tracks the incoming request connections type RequestTracker struct { downstreamHandler http.Handler @@ -300,29 +238,6 @@ func makeRequestsTracker(downstreamHandler http.Handler, log logging.Logger, con } } -// requestTrackedConnection used to track the active connections. In particular, it used to remove the -// tracked connection entry from the RequestTracker once a connection is closed. -type requestTrackedConnection struct { - net.Conn - tracker *RequestTracker -} - -func (c *requestTrackedConnection) UnderlyingConn() net.Conn { - return c.Conn -} - -// Close removes the connection from the tracker's connections map and call the underlaying Close function. -func (c *requestTrackedConnection) Close() error { - c.tracker.hostRequestsMu.Lock() - trackerRequest := c.tracker.acceptedConnections[c.Conn.LocalAddr()] - delete(c.tracker.acceptedConnections, c.Conn.LocalAddr()) - if trackerRequest != nil { - c.tracker.hostRequests.removeTrackedConnection(trackerRequest) - } - c.tracker.hostRequestsMu.Unlock() - return c.Conn.Close() -} - // Accept waits for and returns the next connection to the listener. func (rt *RequestTracker) Accept() (conn net.Conn, err error) { // the following for loop is a bit tricky : @@ -334,7 +249,7 @@ func (rt *RequestTracker) Accept() (conn net.Conn, err error) { return } - trackerRequest := makeTrackerRequest(conn.RemoteAddr().String(), "", "", time.Now(), conn) + trackerRequest := makeTrackerRequest(conn.RemoteAddr().String(), "", "", time.Now()) rateLimitingWindowStartTime := trackerRequest.created.Add(-time.Duration(rt.config.ConnectionsRateLimitingWindowSeconds) * time.Second) rt.hostRequestsMu.Lock() @@ -376,7 +291,6 @@ func (rt *RequestTracker) Accept() (conn net.Conn, err error) { // add an entry to the acceptedConnections so that the ServeHTTP could find the connection quickly. rt.acceptedConnections[conn.LocalAddr()] = trackerRequest rt.hostRequestsMu.Unlock() - conn = &requestTrackedConnection{Conn: conn, tracker: rt} return } } @@ -421,7 +335,7 @@ func (rt *RequestTracker) sendBlockedConnectionResponse(conn net.Conn, requestTi func (rt *RequestTracker) pruneAcceptedConnections(pruneStartDate time.Time) { localAddrToRemove := []net.Addr{} for localAddr, request := range rt.acceptedConnections { - if !request.noPrune && request.created.Before(pruneStartDate) { + if !request.created.Before(pruneStartDate) { localAddrToRemove = append(localAddrToRemove, localAddr) } } @@ -478,14 +392,6 @@ func (rt *RequestTracker) GetTrackedRequest(request *http.Request) (trackedReque return rt.httpConnections[localAddr] } -// GetRequestConnection return the underlying connection for the given request -func (rt *RequestTracker) GetRequestConnection(request *http.Request) net.Conn { - rt.httpConnectionsMu.Lock() - defer rt.httpConnectionsMu.Unlock() - localAddr := request.Context().Value(http.LocalAddrContextKey).(net.Addr) - return rt.httpConnections[localAddr].connection -} - func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http.Request) { // this function is called only after we've fetched all the headers. on some malicious clients, this could get delayed, so we can't rely on the // tcp-connection established time to align with current time. @@ -510,20 +416,16 @@ func (rt *RequestTracker) ServeHTTP(response http.ResponseWriter, request *http. } trackedRequest := rt.acceptedConnections[localAddr] + delete(rt.acceptedConnections, localAddr) if trackedRequest != nil { - // update the original tracker request so that it won't get pruned. - if !trackedRequest.noPrune { - trackedRequest.noPrune = true - rt.hostRequests.convertToAdditionalRequest(trackedRequest) - } // create a copy, so we can unlock - trackedRequest = makeTrackerRequest(trackedRequest.remoteAddr, trackedRequest.remoteHost, trackedRequest.remotePort, trackedRequest.created, trackedRequest.connection) + trackedRequest = makeTrackerRequest(trackedRequest.remoteAddr, trackedRequest.remoteHost, trackedRequest.remotePort, trackedRequest.created) } rt.hostRequestsMu.Unlock() // we have no request tracker ? no problem; create one on the fly. if trackedRequest == nil { - trackedRequest = makeTrackerRequest(request.RemoteAddr, "", "", time.Now(), nil) + trackedRequest = makeTrackerRequest(request.RemoteAddr, "", "", time.Now()) } // update the origin address. diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index 158cf45336..d814507c78 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -51,7 +51,7 @@ func TestHostIncomingRequestsOrdering(t *testing.T) { now := time.Now() perm := rand.Perm(100) for i := 0; i < 100; i++ { - trackedRequest := makeTrackerRequest("remoteaddr", "host", "port", now.Add(time.Duration(perm[i])*time.Minute), nil) + trackedRequest := makeTrackerRequest("remoteaddr", "host", "port", now.Add(time.Duration(perm[i])*time.Minute)) hir.add(trackedRequest) } require.Equal(t, 100, len(hir.requests)) @@ -178,7 +178,7 @@ func TestRemoteAddress(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - tr := makeTrackerRequest("127.0.0.1:444", "", "", time.Now(), nil) + tr := makeTrackerRequest("127.0.0.1:444", "", "", time.Now()) require.Equal(t, "127.0.0.1:444", tr.remoteAddr) require.Equal(t, "127.0.0.1", tr.remoteHost) require.Equal(t, "444", tr.remotePort) diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 8a43ad5234..f222d2ff27 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -1031,17 +1031,6 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo return http.StatusOK } -// GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same -// request that was provided to the http handler ( or provide a fallback Context() to that ) -// if the provided request has no associated connection, it returns nil. ( this should not happen for any http request that was registered -// by WebsocketNetwork ) -func (wn *WebsocketNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettableConn) { - if wn.requestsTracker != nil { - conn = wn.requestsTracker.GetRequestConnection(request) - } - return -} - // ServerHTTP handles the gossip network functions over websockets func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *http.Request) { if !wn.config.EnableGossipService { diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go index 823895a417..d76273de62 100644 --- a/rpcs/ledgerService.go +++ b/rpcs/ledgerService.go @@ -34,7 +34,6 @@ import ( "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/network" ) const ( @@ -63,7 +62,6 @@ type LedgerForService interface { // httpGossipNode is a reduced interface for the gossipNode that only includes the methods needed by the LedgerService type httpGossipNode interface { RegisterHTTPHandler(path string, handler http.Handler) - GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettableConn) } // LedgerService represents the Ledger RPC API @@ -211,17 +209,16 @@ func (ls *LedgerService) ServeHTTP(response http.ResponseWriter, request *http.R response.WriteHeader(http.StatusOK) return } - if conn := ls.net.GetHTTPRequestConnection(request); conn != nil { - maxCatchpointFileWritingDuration := 2 * time.Minute + rc := http.NewResponseController(response) + maxCatchpointFileWritingDuration := 2 * time.Minute - catchpointFileSize, err := cs.Size() - if err != nil || catchpointFileSize <= 0 { - maxCatchpointFileWritingDuration += maxCatchpointFileSize * time.Second / expectedWorstUploadSpeedBytesPerSecond - } else { - maxCatchpointFileWritingDuration += time.Duration(catchpointFileSize) * time.Second / expectedWorstUploadSpeedBytesPerSecond - } - conn.SetWriteDeadline(time.Now().Add(maxCatchpointFileWritingDuration)) + catchpointFileSize, err := cs.Size() + if err != nil || catchpointFileSize <= 0 { + maxCatchpointFileWritingDuration += maxCatchpointFileSize * time.Second / expectedWorstUploadSpeedBytesPerSecond } else { + maxCatchpointFileWritingDuration += time.Duration(catchpointFileSize) * time.Second / expectedWorstUploadSpeedBytesPerSecond + } + if wdErr := rc.SetWriteDeadline(time.Now().Add(maxCatchpointFileWritingDuration)); wdErr != nil { logging.Base().Warnf("LedgerService.ServeHTTP unable to set connection timeout") } From 3bcfedb34871fddba13bfe6125f92b60a975fd06 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:32:26 -0400 Subject: [PATCH 164/218] tests: extend TestP2PRelay logging (#6048) --- network/p2pNetwork_test.go | 48 +++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 3548dbd1cb..7c94be98e4 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -785,13 +785,20 @@ func TestP2PHTTPHandler(t *testing.T) { require.ErrorIs(t, err, limitcaller.ErrConnectionQueueingTimeout) } +// TestP2PRelay checks p2p nodes can properly relay messages: +// netA and netB are started with ForceFetchTransactions so it subscribes to the txn topic, +// both of them are connected and do not relay messages. +// Later, netB is forced to relay messages and netC is started with a listening address set +// so that it relays messages as well. +// The test checks messages from both netB and netC are received by netA. func TestP2PRelay(t *testing.T) { partitiontest.PartitionTest(t) cfg := config.GetDefaultLocal() cfg.ForceFetchTransactions = true log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + log.Debugln("Starting netA") + netA, err := NewP2PNetwork(log.With("net", "netA"), cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) err = netA.Start() @@ -806,7 +813,8 @@ func TestP2PRelay(t *testing.T) { multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} - netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + log.Debugf("Starting netB with phonebook addresses %v", phoneBookAddresses) + netB, err := NewP2PNetwork(log.With("net", "netB"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) err = netB.Start() require.NoError(t, err) @@ -826,8 +834,7 @@ func TestP2PRelay(t *testing.T) { return netA.hasPeers() && netB.hasPeers() }, 2*time.Second, 50*time.Millisecond) - makeCounterHandler := func(numExpected int) ([]TaggedMessageProcessor, *atomic.Uint32, chan struct{}) { - var numActual atomic.Uint32 + makeCounterHandler := func(numExpected int, counter *atomic.Uint32, msgs *[][]byte) ([]TaggedMessageProcessor, chan struct{}) { counterDone := make(chan struct{}) counterHandler := []TaggedMessageProcessor{ { @@ -837,10 +844,13 @@ func TestP2PRelay(t *testing.T) { ProcessorHandleFunc }{ ProcessorValidateFunc(func(msg IncomingMessage) ValidatedMessage { - return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: nil} + return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: msg.Data} }), ProcessorHandleFunc(func(msg ValidatedMessage) OutgoingMessage { - if count := numActual.Add(1); int(count) >= numExpected { + if msgs != nil { + *msgs = append(*msgs, msg.ValidatedMessage.([]byte)) + } + if count := counter.Add(1); int(count) >= numExpected { close(counterDone) } return OutgoingMessage{Action: Ignore} @@ -848,9 +858,10 @@ func TestP2PRelay(t *testing.T) { }, }, } - return counterHandler, &numActual, counterDone + return counterHandler, counterDone } - counterHandler, _, counterDone := makeCounterHandler(1) + var counter atomic.Uint32 + counterHandler, counterDone := makeCounterHandler(1, &counter, nil) netA.RegisterProcessors(counterHandler) // send 5 messages from both netB to netA @@ -866,10 +877,11 @@ func TestP2PRelay(t *testing.T) { case <-time.After(1 * time.Second): } - // add netC with listening address set, and enable relaying on netB - // ensure all messages are received by netA + // add a netC with listening address set and enable relaying on netB + // ensure all messages from netB and netC are received by netA cfg.NetAddress = "127.0.0.1:0" - netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + log.Debugf("Starting netB with phonebook addresses %v", phoneBookAddresses) + netC, err := NewP2PNetwork(log.With("net", "netC"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) err = netC.Start() require.NoError(t, err) @@ -893,28 +905,32 @@ func TestP2PRelay(t *testing.T) { }, 2*time.Second, 50*time.Millisecond) const expectedMsgs = 10 - counterHandler, count, counterDone := makeCounterHandler(expectedMsgs) + counter.Store(0) + var loggedMsgs [][]byte + counterHandler, counterDone = makeCounterHandler(expectedMsgs, &counter, &loggedMsgs) netA.ClearProcessors() netA.RegisterProcessors(counterHandler) for i := 0; i < expectedMsgs/2; i++ { - err := netB.Relay(context.Background(), protocol.TxnTag, []byte{1, 2, 3, byte(i)}, true, nil) + err := netB.Relay(context.Background(), protocol.TxnTag, []byte{5, 6, 7, byte(i)}, true, nil) require.NoError(t, err) err = netC.Relay(context.Background(), protocol.TxnTag, []byte{11, 12, 10 + byte(i), 14}, true, nil) require.NoError(t, err) } // send some duplicate messages, they should be dropped for i := 0; i < expectedMsgs/2; i++ { - err := netB.Relay(context.Background(), protocol.TxnTag, []byte{1, 2, 3, byte(i)}, true, nil) + err := netB.Relay(context.Background(), protocol.TxnTag, []byte{5, 6, 7, byte(i)}, true, nil) require.NoError(t, err) } select { case <-counterDone: - case <-time.After(2 * time.Second): - if c := count.Load(); c < expectedMsgs { + case <-time.After(3 * time.Second): + if c := counter.Load(); c < expectedMsgs { + t.Logf("Logged messages: %v", loggedMsgs) require.Failf(t, "One or more messages failed to reach destination network", "%d > %d", expectedMsgs, c) } else if c > expectedMsgs { + t.Logf("Logged messages: %v", loggedMsgs) require.Failf(t, "One or more messages that were expected to be dropped, reached destination network", "%d < %d", expectedMsgs, c) } } From 1b29523079bebaf1809306efdeb800bc5da70736 Mon Sep 17 00:00:00 2001 From: John Lee Date: Wed, 3 Jul 2024 12:33:40 -0400 Subject: [PATCH 165/218] CI: increase artifact upload timeout (#6050) --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f431e91160..4057049dac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -721,6 +721,7 @@ commands: export GOPATH="<< parameters.build_dir >>/go" export TRAVIS_BRANCH=${CIRCLE_BRANCH} scripts/travis/deploy_packages.sh + no_output_timeout: 20m - when: condition: equal: [ "amd64", << parameters.platform >> ] From b3c7bca66d3dcbccfd5cf7abc5d30620b175406b Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Mon, 8 Jul 2024 09:36:10 -0400 Subject: [PATCH 166/218] Config: Expose merged configuration for easier debugging (#6049) --- cmd/algod/main.go | 9 + daemon/algod/api/algod.oas2.json | 27 + daemon/algod/api/algod.oas3.yml | 26 + .../api/server/v2/generated/data/routes.go | 30 +- .../v2/generated/experimental/routes.go | 448 ++++++------- .../nonparticipating/private/routes.go | 466 +++++++------- .../nonparticipating/public/routes.go | 604 +++++++++--------- .../generated/participating/private/routes.go | 474 +++++++------- .../generated/participating/public/routes.go | 52 +- daemon/algod/api/server/v2/handlers.go | 5 + .../algod/api/server/v2/test/handlers_test.go | 16 + 11 files changed, 1136 insertions(+), 1021 deletions(-) diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 09770cb6e0..c4d1c09bc8 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -17,6 +17,7 @@ package main import ( + "encoding/json" "flag" "fmt" "math/rand" @@ -174,6 +175,14 @@ func run() int { log.Fatalf("Cannot load config: %v", err) } + // log is not setup yet + fmt.Printf("Config loaded from %s\n", absolutePath) + fmt.Println("Configuration after loading/defaults merge: ") + err = json.NewEncoder(os.Stdout).Encode(cfg) + if err != nil { + fmt.Println("Error encoding config: ", err) + } + // set soft memory limit, if configured if cfg.GoMemLimit > 0 { debug.SetMemoryLimit(int64(cfg.GoMemLimit)) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index e0827feb16..4cc1e0ced7 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -216,6 +216,33 @@ } } }, + "/debug/settings/config": { + "get": { + "description": "Returns the merged (defaults + overrides) config file in json.", + "tags": [ + "private" + ], + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Gets the merged config file.", + "operationId": "GetConfig", + "responses": { + "200": { + "description": "The merged config file in json.", + "schema": { + "type": "string" + } + }, + "default": { + "description": "Unknown Error" + } + } + } + }, "/v2/accounts/{address}": { "get": { "description": "Given a specific account public key, this call returns the accounts status, balance and spendable amounts", diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 3fb04f5add..2f57b62453 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -2788,6 +2788,32 @@ }, "openapi": "3.0.1", "paths": { + "/debug/settings/config": { + "get": { + "description": "Returns the merged (defaults + overrides) config file in json.", + "operationId": "GetConfig", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "The merged config file in json." + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Gets the merged config file.", + "tags": [ + "private" + ] + } + }, "/debug/settings/pprof": { "get": { "description": "Retrieves the current settings for blocking and mutex profiles", diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index dc0159d20d..4c32b80945 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -320,21 +320,21 @@ var swaggerSpec = []string{ "+Omj/1j85eFnD3N48tkXDx/yL57wR198+gge/+WzJw/h0fLzLxaPi8dPHi+ePH7y+Wdf5J8+ebR48vkX", "/3HP8SEHMgEaUvs/nf2v7Kxcqezs5Xn22gHb4oRX4ltwe4O68lJhXUuH1BxPImy4KGdPw0//I5ywk1xt", "2uHDrzNfn2m2trYyT09Pr6+vT+Iupyt8+p9ZVefr0zAPVhvsyCsvz5sYfYrDwR1trce4qZ4UzvDbq68u", - "XrOzl+cnLcHMns4enjw8eeRLW0teidnT2af4E56eNe77KebXPDU+df5pVVHy/A/z2amnQ//XGniJSXTc", - "HxuwWuThkwZe7Pz/zTVfrUCf4AsN+unq8WmQOE7f++wIH/Z9O42jP07fd5JIFAd6huiGQ01O34fyuPsH", - "7JRG9XFlUYeJgO5rdrrAkjhTm0K8uvGloKpiTt+jsD36+6m3mKQ/otJDp+k0JGMZaUnP7tMfOyh8b7du", - "IfuHc22i8XJu83Vdnb7H/+DBiFZEWTxP7VaeopP49H0HEf7zABHd39vucYurjSogAKeWS6opvO/z6Xv6", - "N5oIthVo4SROzJzjf6UMZ6dYWm43/HknvUuzhFRemh+lAdKIQ1WBnczb520NrzgvQuOLncyDaBziHpED", - "PH74kKZ/gv+Z+dJLvewtp/48z0xTa36vYaaTNxP5a88m18BLj/jAnswQhkcfD4ZzSbGOjuHSxfBhPvvs", - "Y2LhXDoZhpcMW9L0n37ETQB9JXJgr2FTKc21KHfsR9mEa0aFcFMUeCnVtQyQO6mi3my43qG0vlFXYJiv", - "sRsRJ9Pg5CMK6UA3f0vDeK1xx0fezKp6UYp8Nqcsqe9QIrMp4SQYioYzBSNZO3j3VHxz8ExM34WuzLsn", - "Lc0kOA8kLKDhhwL7cH/D3vfdrDTVvdQGzf7FCP7FCO6QEdhay9EjGt1fmFsNKv+MNef5Gvbxg+FtGV3w", - "s0qlkkdc7GEWvoLJGK+46PKKNpxw9vTNtAJ/3rNBRusCjDvMJ0FhcdJ4q0/ohiOFM49+1Wiv99Uu//Du", - "D3G/P+MynOfOjpPrkutSgG6ogMthUZl/cYH/Z7gAVcfitK9zZqEsTXz2rcKzT14enzJTkvdtIh/oZDht", - "henOz6fBNpHSQbst33f+7OpVZl3bQl1Hs6BVn1xSQy3DfaxN/+/Tay5stlTaJ9bkSwt62NkCL099FZ3e", - "r23i+sEXzMYf/Rg/GU3+esq9upH6hrxurONAH0599SrfSKMQ6Rw+t5a12FKFfLaxUb1557gcFlr3LLg1", - "vDw9PcWnL2tl7Onsw/x9zygTf3zXEFaoDzqrtLjCOgbv5rNtprRYCcnLzFs12lJgs8cnD2cf/m8AAAD/", - "/xb9Ejq6CAEA", + "XrOzl+cnLcHMns4enjw8eeRLW0teidnT2af4E56eNe77KebXPDU+df5p81brw3zwraoosb775GnU/7UG", + "XmKCHffHBqwWefikgRc7/39zzVcr0Cf4eoN+unp8GqSR0/c+c8KHfd9O48iQ0/edBBPFgZ4h8uFQk9P3", + "oXTu/gE7ZVN9zFnUYSKg+5qdLrBcztSmEK9ufCmoxpjT9yiIj/5+6q0p6Y+oENFJOw2JWkZa0pP89McO", + "Ct/brVvI/uFcm2i8nNt8XVen7/E/eGiiFVGGz1O7lafoQD5930GE/zxARPf3tnvc4mqjCgjAqeWS6g3v", + "+3z6nv6NJoJtBVo4aRSz6vhfKfvZKZad2w1/3knv7iwhlbPmR2mAtOVQcWAn8/bpW8NHzovQ+GIn8yA2", + "h5hI5A6PHz6k6Z/gf2a+LFMvs8upP88z09Sh32u06eTURN7bs9c18NIDP7AnM4Th0ceD4VxSHKRjxnRp", + "fJjPPvuYWDiXTr7hJcOWNP2nH3ETQF+JHNhr2FRKcy3KHftRNqGcUZHcFAVeSnUtA+RO4qg3G653KMlv", + "1BUY5uvvRsTJNDjZicI9MASgpWG88rjjI29mVb0oRT6bUwbVdyit2ZTgEoxIw5mCAa0dvHsqvjl4Jqbv", + "Qlce3pOyZhKcB5IZ0PBDYX64v2Hv+y5YmupeaoNm/2IE/2IEd8gIbK3l6BGN7i/MuwaVf+Ka83wN+/jB", + "8LaMLvhZpVKJJS72MAtf3WSMV1x0eUUbajh7+mZa8T/v9SCDdgHGHeaToMw4Sb3VNXTDkcKZR59rtNf7", + "6pp/ePeHuN+fcRnOc2fHya3JdSlAN1TA5bDgzL+4wP8zXIAqZ3Ha1zmzUJYmPvtW4dknD5BPpynJMzeR", + "D3Syn7bCdOfn02C3SOmg3ZbvO3929Sqzrm2hrqNZ0OJP7qqhluE+1qb/9+k1FzZbKu2TbvKlBT3sbIGX", + "p77CTu/XNqn94Atm6o9+jJ+TJn895V7dSH1DXjfWcaAPp756lW+kUYiCDp9bq1tsxUI+29iv3rxzXA6L", + "sHsW3Bplnp6e4rOYtTL2dPZh/r5nsIk/vmsIK9QOnVVaXGGNg3fz2TZTWqyE5GXmrRptmbDZ45OHsw//", + "NwAA//+q4vIh1ggBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index 5d9044ed56..83312b906f 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -131,230 +131,230 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go index 7e8da1ba91..aee8f09bc9 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -21,6 +21,9 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + // Gets the merged config file. + // (GET /debug/settings/config) + GetConfig(ctx echo.Context) error // (GET /debug/settings/pprof) GetDebugSettingsProf(ctx echo.Context) error @@ -43,6 +46,17 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// GetConfig converts echo context to params. +func (w *ServerInterfaceWrapper) GetConfig(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetConfig(ctx) + return err +} + // GetDebugSettingsProf converts echo context to params. func (w *ServerInterfaceWrapper) GetDebugSettingsProf(ctx echo.Context) error { var err error @@ -158,6 +172,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.GET(baseURL+"/debug/settings/config", wrapper.GetConfig, m...) router.GET(baseURL+"/debug/settings/pprof", wrapper.GetDebugSettingsProf, m...) router.PUT(baseURL+"/debug/settings/pprof", wrapper.PutDebugSettingsProf, m...) router.DELETE(baseURL+"/v2/catchup/:catchpoint", wrapper.AbortCatchup, m...) @@ -169,231 +184,232 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9f5PbtpLgV0Fpt8qxT5yxHSf74qtXexM7yZuLk7g8Tt7t2r4EIlsS3lAAHwDOSPH5", - "u1+hGyBBEpSomYmT1O5f9oj40Wg0Go3++X6Wq02lJEhrZk/fzyqu+QYsaPyL57mqpc1E4f4qwORaVFYo", - "OXsavjFjtZCr2Xwm3K8Vt+vZfCb5Bto2rv98puGftdBQzJ5aXcN8ZvI1bLgb2O4q17oZaZutVOaHOKMh", - "zp/PPuz5wItCgzFDKH+Q5Y4JmZd1AcxqLg3P3SfDroVdM7sWhvnOTEimJDC1ZHbdacyWAsrCnIRF/rMG", - "vYtW6ScfX9KHFsRMqxKGcD5Tm4WQEKCCBqhmQ5hVrIAlNlpzy9wMDtbQ0CpmgOt8zZZKHwCVgIjhBVlv", - "Zk/fzAzIAjTuVg7iCv+71AC/Qma5XoGdvZunFre0oDMrNomlnXvsazB1aQ3DtrjGlbgCyVyvE/ZdbSxb", - "AOOSvfr6Gfv000+/cAvZcGuh8EQ2uqp29nhN1H32dFZwC+HzkNZ4uVKayyJr2r/6+hnOf+EXOLUVNwbS", - "h+XMfWHnz8cWEDomSEhICyvchw71ux6JQ9H+vICl0jBxT6jxnW5KPP/vuis5t/m6UkLaxL4w/Mroc5KH", - "Rd338bAGgE77ymFKu0HfPMy+ePf+0fzRww//8uYs+0//52effpi4/GfNuAcwkGyY11qDzHfZSgPH07Lm", - "coiPV54ezFrVZcHW/Ao3n2+Q1fu+zPUl1nnFy9rRici1OitXyjDuyaiAJa9Ly8LErJalY1NuNE/tTBhW", - "aXUlCijmjvter0W+Zjk3NAS2Y9eiLB0N1gaKMVpLr27PYfoQo8TBdSN84IL+uMho13UAE7BFbpDlpTKQ", - "WXXgego3DpcFiy+U9q4yx11W7PUaGE7uPtBli7iTjqbLcscs7mvBuGGchatpzsSS7VTNrnFzSnGJ/f1q", - "HNY2zCENN6dzj7rDO4a+ATISyFsoVQKXiLxw7oYok0uxqjUYdr0Gu/Z3ngZTKWmAqcU/ILdu2//3xQ/f", - "M6XZd2AMX8FLnl8ykLkqoDhh50smlY1Iw9MS4tD1HFuHhyt1yf/DKEcTG7OqeH6ZvtFLsRGJVX3Ht2JT", - "b5isNwvQbkvDFWIV02BrLccAohEPkOKGb4eTvta1zHH/22k7spyjNmGqku8QYRu+/evDuQfHMF6WrAJZ", - "CLliditH5Tg392HwMq1qWUwQc6zb0+hiNRXkYimgYM0oeyDx0xyCR8jj4GmFrwicMMgoOM0sB8CRsE3Q", - "jDvd7gur+AoikjlhP3rmhl+tugTZEDpb7PBTpeFKqNo0nUZgxKn3S+BSWcgqDUuRoLELjw7HYKiN58Ab", - "LwPlSlouJBSOOSPQygIxq1GYogn3v3eGt/iCG/j8ydgd336duPtL1d/1vTs+abexUUZHMnF1uq/+wKYl", - "q07/Ce/DeG4jVhn9PNhIsXrtbpulKPEm+ofbv4CG2iAT6CAi3E1GrCS3tYanb+UD9xfL2IXlsuC6cL9s", - "6Kfv6tKKC7FyP5X00wu1EvmFWI0gs4E1+eDCbhv6x42XZsd2m3xXvFDqsq7iBeWdh+tix86fj20yjXks", - "YZ41r9344fF6Gx4jx/aw22YjR4AcxV3FXcNL2Glw0PJ8if9sl0hPfKl/df9UVel622qZQq2jY38lo/rA", - "qxXOqqoUOXdIfOU/u6+OCQA9JHjb4hQv1KfvIxArrSrQVtCgvKqyUuW8zIzlFkf6Vw3L2dPZv5y2+pdT", - "6m5Oo8lfuF4X2MmJrCQGZbyqjhjjpRN9zB5m4Rg0fkI2QWwPhSYhaRMdKQnHgku44tKetE+WDj9oDvAb", - "P1OLb5J2CN+9J9gowhk1XIAhCZga3jMsQj1DtDJEKwqkq1Itmh8+OauqFoP4/ayqCB8oPYJAwQy2wlhz", - "H5fP25MUz3P+/IR9E4+NoriS5c5dDiRquLth6W8tf4s1uiW/hnbEe4bhdip94rYmoMGJ+XdBcfisWKvS", - "ST0HacU1/ptvG5OZ+31S5z8HicW4HScufGh5zNEbB3+JHjef9ChnSDhe3XPCzvp9b0Y2bpQ9BGPOWyze", - "NfHgL8LCxhykhAiiiJr89nCt+W7mhcQMhb0hmfxogCik4ishEdq5ez5JtuGXtB8K8e4IAUzzLiJaIgmy", - "UaF6mdOj/mSgZ/kTUGtqY4Mk6iTVUhiL72pszNZQouDMZSDomFRuRBkTNnzPIhqYrzWviJb9FxK7hMT3", - "PDUiWG958U68E5MwR+w+2miE6sZs+SDrTEKCXKMHw5elyi//xs36Dk74Iow1pH2chq2BF6DZmpt14uD0", - "aLsdbQp9u4ZIs2wRTXXSLPGFWpk7WGKpjmFdVfWMl6WbesiyeqvFgScd5LJkrjGDjUCFuX84koad3l/s", - "K56vnVjAcl6W81ZVpKqshCso3aNdSAl6zuya2/bw48jhXYPnyIBjdhZYtBqvZkIVm250ERrYhuMNtHGv", - "mars9mk4qOEb6ElBeCOqGrUI0UPj/HlYHVyBRJ7UDI3gN2tEbU08+Imb23/CmaWixZEG0AbzXYO/hl90", - "gHat2/tUtlMoXZDO2rrfhGa50jQE3fB+cvcf4LrtTNT5SaUh80NofgXa8NKtrreo+w353tXpPHAyC255", - "dDI9FaYfYMQ5sB+Kd6ATWpof8D+8ZO6zk2IcJbXUI1AYUZE5taCL2aGKZnINUN+q2IZUmazi+eVRUD5r", - "J0+zmUkn7yvSnvot9Itoduj1VhTmrrYJBxvbq+4JId1VYEcDWWQv04nmmoKA16pixD56IBCnwNEIIWp7", - "59fal2qbgulLtR1caWoLd7ITbpzJzP5LtX3uIVP6MOZx7ClIdwuUfAMGbzcZM043S2uXO1sofTNponfB", - "SNZaGxl3o0bC1LyHJGxaV5k/mwmLBTXoDdQ6eOwXAvrDpzDWwcKF5b8BFowb9S6w0B3orrGgNpUo4Q5I", - "f50U4hbcwKeP2cXfzj579Pjnx5997kiy0mql+YYtdhYM+8Sr5ZixuxLuJ19HKF2kR//8SbBRdcdNjWNU", - "rXPY8Go4FNm+6PVLzZhrN8RaF8246gbASRwR3NVGaGdk1nWgPYdFvboAa91L96VWyzvnhoMZUtBho5eV", - "doKF6doJvbR0Wrgmp7C1mp9W2BJkQX4Gbh3CuDfgZnEnRDW28UU7S8E8Rgs4eCiO3aZ2ml28VXqn67tQ", - "b4DWSiev4Eorq3JVZk7OEyqhoHjpWzDfImxX1f+doGXX3DA3N1ova1mM6CHsVk6/v2jo11vZ4mbvDUbr", - "TazOzztlX7rIb18hFejMbiVD6uyoR5ZabRhnBXZEWeMbsCR/iQ1cWL6pflgu70bbqXCghB5HbMC4mRi1", - "cNKPgVxJcuY7oLLxo05BTx8xwcpkxwHwGLnYyRxNZXdxbMe1WRsh0W5vdjKPVFsOxhKKVYcsb6/CGkMH", - "TXXPJMBx6HiBn1FX/xxKy79W+nUrvn6jVV3dOXvuzzl1OdwvxlsDCtc3qIGFXJVdB9KVg/0ktcbfZUHP", - "GiUCrQGhR4p8IVZrG70XX2r1G9yJyVlSgOIHUhaVrs9QZfS9KhwzsbW5A1GyHazlcI5uY77GF6q2jDOp", - "CsDNr01ayBxxOURfJ3TRsrHcivoJYdgCHHXlvHarrSuGDkiD+6LtmPGcTmiGqDEj7heN3wy1ounIna3U", - "wIsdWwBIphbex8F7X+AiOXpP2SCmeRE3wS86cFVa5WAMFJlXRR8ELbSjq8PuwRMCjgA3szCj2JLrWwN7", - "eXUQzkvYZejrZ9gn3/5k7v8O8FpleXkAsdgmhd6+Pm0I9bTp9xFcf/KY7EhTR1TrxFvHIEqwMIbCo3Ay", - "un99iAa7eHu0XIFGl5LflOLDJLcjoAbU35jebwttXY14sPtnupPw3IZJLlUQrFKDldzY7BBbdo06ugS3", - "gogTpjgxDjwieL3gxpIblJAF6jTpOsF5SAhzU4wDPPoMcSP/FF4gw7Fzdw9KU5vmOWLqqlLaQpFaA1pk", - "R+f6HrbNXGoZjd28eaxitYFDI49hKRrfI8u/gPEPbhv7q7foDheHNnV3z++SqOwA0SJiHyAXoVWE3diL", - "dwQQYVpEE+EI06OcxnV4PjNWVZXjFjarZdNvDE0X1PrM/ti2HRIXGTno3i4UGDSg+PYe8mvCLPlvr7lh", - "Ho5gYkd1DvlrDWF2hzEzQuaQ7aN8fOK5VvEROHhI62qleQFZASXfJZwD6DOjz/sGwB1vn7vKQkaOuOlN", - "byk5+D3uGVrheCYlPDL8wnJ3BN1ToCUQ3/vAyAXg2Cnm5OnoXjMUzpXcojAeLpu2OjEi3oZXyrod9/SA", - "IHuOPgXgETw0Q98cFdg5a9+e/Sn+A4yfoJEjjp9kB2ZsCe34Ry1gRBfsY5yi89Jj7z0OnGSbo2zsAB8Z", - "O7IjiumXXFuRiwrfOt/C7s6ffv0JkoZzVoDlooSCRR/oGVjF/Rm5kPbHvNlTcJLubQj+QPmWWE5w0+kC", - "fwk7fHO/pNiESNVxF2/ZxKjufuKSIaDB49mJ4HET2PLcljsnqNk17Ng1aGCmXpALw9CeYlWVxQMk7TN7", - "ZvTW2aRtdK+5+AKHipaX8jWjN8F++F73HgYddPi3QKVUOUFDNkBGEoJJviOsUm7XhQ9/CgEwgZI6QHqm", - "jab55vq/ZzpoxhWw/1A1y7nEJ1dtoZFplEZBAQVIN4MTwZo5vXNiiyEoYQP0ksQvDx70F/7ggd9zYdgS", - "rkPMoGvYR8eDB6jHeamM7RyuO9CHuuN2nrg+0HDlLj7/CunzlMMeT37kKTv5sjd4Y+1yZ8oYT7hu+bdm", - "AL2TuZ2y9phGpnl74biTbDld/6DBunHfL8SmLrm9C6sVXPEyU1egtSjgICf3Ewslv7ri5Q9NN4yHhNzR", - "aA5ZjlF8E8eC164PBf65cYQU7gCT0/9UgOCcel1QpwNPzNZTVWw2UAhuodyxSkMOFO/mJEfTLPWEkSd8", - "vuZyhQ8GreqVd26lcZDh14ZUM7qWgyGSQpXdygyV3KkLwLuphZBHJ04Bd0+6voacHjDXvJnPR7lOuZmj", - "PehbDJJGsvls9MXrkHrVvngJOd24zQmXQUfei/DTTjzRlIKoc7LPEF/xtrjD5Db3t1HZt0OnoBxOHHn8", - "th/HnH7dc7vc3YHQQwMxDZUGg1dUrKYy9FUt4xjt4Cq4MxY2Q00+df155Pi9Gn0vKlkKCdlGSdgl05II", - "Cd/hx+RxwmtypDMKLGN9+2+QDvw9sLrzTKHG2+IXd7t/QvsWK/O10ndlEqUBJ4v3EyyQB83tfsqb2kl5", - "WSZMiz6Cs88AzLxx1hWacWNULlBmOy/M3HsFkzXSh3t20f+yiUu5g7PXH7dnQ4uTA6COGMqKcZaXAjXI", - "Shqr69y+lRx1VNFSE05c4TE+rrV8Fpqk1aQJLaYf6q3k6MDXaK6SDhtLSKhpvgYIyktTr1ZgbO+tswR4", - "K30rIVkthcW5Nu64ZHReKtDoSXVCLTd8x5aOJqxiv4JWbFHbrvSPAcrGirL0Bj03DVPLt5JbVgI3ln0n", - "5OstDheM/uHISrDXSl82WEjf7iuQYITJ0s5m39BX9Ov3y197H390d6fPwem0zZgwc8vsJEn5v5/8+9M3", - "Z9l/8uzXh9kX/+P03fsnH+4/GPz4+MNf//r/uj99+uGv9//9X1M7FWBPhc96yM+f+5fx+XN8/kSu+n3Y", - "P5r+fyNkliSy2JujR1vsE0wV4Qnoflc5ZtfwVtqtdIR0xUtRON5yE3Lo3zCDs0ino0c1nY3oKcPCWo98", - "VNyCy7AEk+mxxhtLUUP/zHSgOholfew5npdlLWkrg/RNcZjBv0wt500yAspT9pRhpPqaBydP/+fjzz6f", - "zdsI8+b7bD7zX98lKFkU21QegQK2qbdiHCRxz7CK7wzYNPdA2JOudOTbEQ+7gc0CtFmL6uNzCmPFIs3h", - "QsiS1zlt5bkkB393ftDEufOWE7X8+HBbDVBAZdep/EUdQQ1btbsJ0HM7qbS6Ajln4gRO+jqfwr0XvVNf", - "CXwZHFO1UlNeQ805IEILVBFhPV7IJMVKin564Q3+8jd3/hzyA6fg6s+Z8ui9981Xr9mpZ5jmHqW0oKGj", - "JASJp7QPnuw4JDluFseUvZVv5XNYovZByadvZcEtP11wI3JzWhvQX/KSyxxOVoo9DfGYz7nlb+VA0hpN", - "rBgFTbOqXpQiZ5fxg6QlT0qWNRzh7ds3vFypt2/fDXwzhs8HP1WSv9AEmROEVW0zn+on03DNdcr2ZZpU", - "Lzgy5fLaNysJ2aomBWlIJeTHT/M8XlWmn/JhuPyqKt3yIzI0PqGB2zJmrGri0ZyA4kN63f5+r/zFoPl1", - "0KvUBgz7ZcOrN0Ladyx7Wz98+ClG9rU5EH7xV76jyV0Fk7Uroykp+koVXDg9K9FXPav4KmVie/v2jQVe", - "4e6jvLxBHUdZMuzWiToMAQY4VLuAJsR5dAMIjqODg3FxF9QrpHVMLwE/4RZ2A7BvtV9R/PyNt+tADD6v", - "7TpzZzu5KuNIPOxMk+1t5YSs4I1hxApfqz4x3gJYvob80mcsg01ld/NO9+Dw4wXNwDqEoVx2FGGI2ZTQ", - "QLEAVlcF96I4l7t+WhtDERU46Cu4hN1r1SZjOiaPTTetihk7qEipkXTpiDU+tn6M/uZ7r7IQaOqzk2Dw", - "ZiCLpw1dhD7jB5lE3js4xCmi6KT9GEME1wlEEPGPoOAGC3Xj3Yr0U8sTMgdpxRVkUIqVWKTS8P59aA8L", - "sDqq9JkHvRdyM6BhYsncU35BF6t/3msuV+CuZ3elKsNLyqqadNrA99AauLYL4Havnl/GCSkCdPikvMbI", - "a9Twzd0SYOv2W1jU2Em4dq8KVBRRG++9fDLuf0aAQ3FDeEL39qVwMvrW9ahLZBwMt3KD3eZZ613zYjpD", - "uOj7BjBlqbp2++KgUD7bJiV1ie6X2vAVjLxdYuvdxHwYHYsfDnJIIknKIGrZFzUGkkASZGqcuTUnzzC4", - "L+4Q4zOz55AZZiIDsbcZYRJtj7BFiQJs47lKe891x4pKWYHHQEuzFtCyFQUDGF2MxMdxzU04jpgvNXDZ", - "SdLZb5j2ZV9quvPIlzBKitokngu3YZ+DDt79PkFdyEoXUtHFj/4JaeXc2wvDF1LboSSKpgWUsKKFU+NA", - "KG3CpHaDHBw/LJfIW7KUW2KkoI4EAD8HuJfLA8bINsImj5Ai4whsdHzAgdn3Kj6bcnUMkNInfOJhbLwi", - "or8hHdhHjvpOGFWVu1zFiL0xDxzAp6JoJYueRzUOw4ScM8fmrnjp2Jx/i7eDDDKk4YOilw/Nu97cH3to", - "7DFN0ZV/1JpISLjJamJpNgCdFrX3QLxQ24wilJNvkcV24eg9GbuA8dKpg0m56O4ZtlBbdOfCq4V85Q/A", - "Mg5HACPSvWyFQXrFfmNyFgGzb9r9cm6KCg2SjFe0NuQyJuhNmXpEthwjl0+i9HI3AqCnhmprNXi1xEH1", - "QVc8GV7m7a02b9OmhrCw1PEfO0LJXRrB31A/1k0I97c28d94crFwoj5KJryhZuk2GQqpc0VZB49JUNgn", - "hw4Qe7D6si8HJtHa9fXq4jXCWoqVOOY7NEoO0WagBHwEZx3RNLtMeQq4tzzgPX4RukXKOtw9Lnf3IwdC", - "DSthLLRGo+AX9Huo4zmmT1ZqOb46W+mlW98rpZrLn8zm2LGzzI++AvTAXwptbIYWt+QSXKOvDSqRvnZN", - "0xJo10WRig2IIs1xcdpL2GWFKOs0vfp5v33upv2+uWhMvcBbTEhy0FpgcYyk4/Keqcm3fe+CX9CCX/A7", - "W++00+Cauom1I5fuHH+Sc9FjYPvYQYIAU8Qx3LVRlO5hkFHA+ZA7RtJo5NNyss/aMDhMRRj7oJdaCHsf", - "u/lppORaojSA6QhBtVpBEdKbBXuYjJLIlUquoipOVbUvZ94Jo9R1mHluT9I674YPY074kbifCVnANg19", - "/CpAyNvIOky4h5OsQFK6krRaKIma2MUfW0S6uo9sC+0HACSdoF/3jNmtdzLtUrOduAEl8MK/SQyE9e0/", - "lsMN8aibj7lPdzKf7j9COCDSlLBRYZNhGoIRBsyrShTbnuGJRh1VgvGjtMsj0hayFj/YAQx0naCTBNdJ", - "pe1drb2C/RTfvKfuVUa+196x2NE3z30AflFrtGB0PJuHedubt9rEtX/704VVmq/AW6EyAulWQ+ByjkFD", - "lBXdMCvInaQQyyXE1hdzE8tBB7iBjr2YQLoJIkubaGoh7edPUmR0gHpaGA+jLE0xCVoYs8m/Hlq5gkwf", - "qZKaKyHamhuYqpLh+t/CLvuJl7V7ZAhtWvdcb3bqXr5H7PrV5lvY4cgHvV4dYAd2BTVPrwBpMKXpbz6Z", - "KIH1PdNJ8Y/Py84WHrFTZ+lduqOt8UUZxom/vWU6RQu6S7nNwWidJBwsU3bjIu2b4E4PdBHfJ+VDmyCK", - "wzJIJO/HUwkTSlgOr6ImF8Uh2n0NvAzEi8uZfZjPbucJkLrN/IgHcP2yuUCTeEZPU7IMdxx7jkQ5ryqt", - "rniZeX+Jsctfqyt/+WPz4F7xkV8yacp+/dXZi5ce/A/zWV4C11mjCRhdFbar/jSrojIO+68SyvbtFZ2k", - "KYo2v8nIHPtYXGNm756yaVAUpfWfiY6i97lYph3eD/I+7+pDS9zj8gNV4/HT2jzJ4afr5MOvuCiDsTFA", - "O+KcjoubVlknyRXiAW7tLBT5fGV3ym4Gpzt9OlrqOsCTcK4fMDVl+sUhfeJKZEXe+YffufT0tdId5u8j", - "E5POQ7+dWOWEbMLjiK92qF/ZF6ZOGAlev6x+cafxwYP4qD14MGe/lP5DBCD+vvC/4/viwYOk9TCpxnJM", - "ArVUkm/gfhNlMboRH/cBLuF62gV9drVpJEs1ToYNhZIXUED3tcfetRYen4X/pYAS3E8nUx7p8aYTumNg", - "ppygi7FIxMbJdEMlMw1Tsu9TjUGwjrSQ2fuSDGSMHR4hWW/QgJmZUuRp1w65MI69SnKmdI0ZNh7R1roR", - "azHimytrEY3lmk3JmdoDMpojiUyTTNva4m6h/PGupfhnDUwU7lWzFKDxXutddeFxgKMOBNK0XswPTHaq", - "dvjb6EH22JuCLmifEmSv/e55Y1MKC00V/TnSAzyeccC493hve/rw1EzRbOuuC+a0d8yU0umB0Xlj3cgc", - "yVLowmRLrX6FtCEE7UeJRBjB8ClQzfsryJTnXp+lNEbltqJ7O/uh7Z7+Nh7b+Fu/hcOim6pjN7lM06f6", - "uI28yaPXpNM1eySPPcJiD4NuaMAIa8HjFTnDYhmU4H3EJZ0nygLRiTBLn8o4lvOUxm9PpYd5EP9a8usF", - "T9WIcW8hB1O0vR0/KatY6Bw2wDQ5Dmh2FnlwN20FZZKrQLc2iGFW2hu+a2jayS+a9gGDFBU/XebkplAa", - "lRimltdcUhVx14/4le9tgEzwrte10pgH0qRdugrIxSapjn379k2RD913CrESVCC7NhBVYPYDMUo2iVTk", - "q1g3mTs8as6X7OE8KgPvd6MQV8KIRQnY4hG1WHCD12VjDm+6uOWBtGuDzR9PaL6uZaGhsGtDiDWKNW9P", - "FPIax8QF2GsAyR5iu0dfsE/QJdOIK7jvsOiFoNnTR1+gQw398TB1y/oC5/tYdoE8Ozhrp+kYfVJpDMck", - "/ahp7+ulBvgVxm+HPaeJuk45S9jSXyiHz9KGS76CdHzG5gBM1Bd3E835PbxIsgaAsVrtmLDp+cFyx59G", - "Yr4d+yMwWK42G2E33nHPqI2jp7a8Mk0ahqNa/75eVIArfET/1yq4//V0XR/5GcM3IzFb6KX8PdpoY7TO", - "Gafkn6VoPdNDvU52HnILYwGtpm4W4cbN5ZaOsiQ6qi9ZpYW0qP+o7TL7i3sWa5479ncyBm62+PxJohBV", - "t1aLPA7wj453DQb0VRr1eoTsg8zi+7JPpJLZxnGU4n6bYyE6laOOummXzDG/0P1DT5V83SjZKLnVHXLj", - "Eae+FeHJPQPekhSb9RxFj0ev7KNTZq3T5MFrt0M/vnrhpYyN0qmCAe1x9xKHBqsFXGHEXHqT3Ji33Atd", - "TtqF20D/+/o/BZEzEsvCWU4+BCKL5r5geSfF//Rdm/kcDasUidjTASqd0HZ6vd1H9jY8TuvWt9+Swxh+", - "G8HcZLThKEOsjHjfk3t90+f38Bfqg0R73lE4PvqFafcGRzn+wQME+sGDuReDf3nc/Uzs/cGDdALipMrN", - "/dpi4TYvYuyb2sMvVUIBFqoWNg5FPj9CQgE5dkm5D44JLvxQc9atEPfxpYi7ie9Ke5umT8Hbt2/wS8AD", - "/tFHxO/MLHED2yiF8cPerZCZJJmi+R75uXP2pdpOJZzeHRSI5w+AohGUTFTP4UoGFUCT5vqD/iIRjbpR", - "F1Aq98iMiwLF+vw/D57d4ud7sF2Lsvipze3Wu0g0l/k66SW8cB1/Jhm9cwUTq0zWGVlzKaFMDkdv25/D", - "GzjxSv+HmjrPRsiJbfsVaGm5vcW1gHfBDECFCR16hS3dBDFWu2mzmrQM5UoVDOdpi1q0zHFYyjlVQjMR", - "34zDbmrr/VYxFtwnHFqKEt0w03ZjbJlpbkcSaGG981BfyI2D5ccNqRlodNCMiw1ezIZvqhLwZF6B5ivs", - "qiT0umMKNRw5qljBTOU+YUtMWKGYrbVkarmMlgHSCg3lbs4qbgwN8tAtC7Y49+zpo4cPk2ovxM6ElRIW", - "wzJ/aJfy6BSb0BdfZIlKARwF7GFYP7QUdczGDgnH15T8Zw3GpngqfqDIVbSSulub6kk2tU9P2DeY+cgR", - "cSfVPaorQxLhbkLNuioVL+aY3Pj1V2cvGM1KfaiEPNWzXKG2rkv+SfPK9ASjIbPTSOac6ePsT+XhVm1s", - "1pSfTOUmdC3aApmi53ODerwYOyfsOalQmwL+NAnDFNl6A0VU7ZIe8Ugc7j/W8nyNusmOBDTOK6cXYg3s", - "rLXcRNGHTfUjZNgObl+LlUqxzpmya9DXwgBG5MMVdNMhNrlBvW48pEfsLk/XUhKlnBwhjDa1jo5FewCO", - "JNngVJCErIf4IzVTVI/52Lq0F9grHYvRK3Lbs/qH5HohxTb7zhsXci6VFDmWQkhJ0pi6bZqZckLViLR9", - "0cz8CU0crmRp3SYW2GNxtNhuYIQecUOTf/TVbSpRB/1pYetLrq3AGs/ZoJiHStfeICakAV/NyhFRzCeV", - "Tjg1JQMhGgeKI8kIszKNaDi/dt++9/pvTIpxKSRqujza/PuMTFalEWiZlkxYtlJg/Hq60Tzmjetzglka", - "C9i+O3mhViK/ECscg9zo3LLJZ3Q41FnwIPUem67tM9fW585vfu64g9GkZ1XlJx2vg54UJO1WjiI45bcU", - "HEki5Dbjx6PtIbe9rt94nzpCgyv0WoMK7+EBYTS1tLujfOXelkRR2IJRRGUyga6QCTBeCBlMqOkLIk9e", - "CbgxeF5H+plcc0tvh0k87TXwciQAAiOUyQZ/26H6lQMcSnCNYY7xbWzLgI8wjqZBK/FzuWPhUDjqjoSJ", - "Z7xsXKcTRb1RqvJCVIHBRb0y3ynG4Rh3FkImO+g6GL7XdMdqHMfeRGM5Chd1sQKb8aJIpbb6Er8y/BqC", - "xGALed0UoWqiA7s5yofU5ifKlTT1Zs9cocEtp4vq5ieoIa7dH3YYM+0sdvhvqgLT+M54p+mjo3KDh3Rx", - "XGL+YZRxSup1NJ0ZscqmYwLvlNujo536ZoTe9r9TSg/hun+IaNwel4v3KMXfvnIXR5y4d+CfTldLk1cX", - "fcEVfg8Jj5qMkF2uhFfZoM4Yej3g5iW2rAd8aJgE/IqXI5Hwsa2E7leyH4zFw+ej6Ru49em5LGd7WdBo", - "yiPyFe5ZX4YmxDH/YHIPvjurhV/rXoSO2+6+7VjqyEesZRajFrqbGdHaDT7Wivbt1ViKhFCnA7/H9UC8", - "F8/cp4GHK6Hq4H0VfKDDk5B+9Sl4OnU/RtafjCz4va0WozaW175+LS3Tv8m//YmssAyk1bs/gMVlsOn9", - "ojIJaZfUU20T1pQ+nFQKsXMrTqlhkyqX4mXDoCsj1tKhpUH5mQFZPZ8iDgzw8WE+Oy+OujBTJXdmNErq", - "2L0Qq7XFjP1/A16AfnmgIkFbhQCPWKWMaCuQlm4wnwJ2jcOdTA02cAQs4ooKw7GCE+oV5BbLzrbOdRrg", - "mPoKbrJg9PnvygTjz+kmJsMXJNhXhWBYa/bAHT9InBQl/6I6nSfTc+6fNS7UFAF2zU2brqUXMz05cnO5", - "hByzIu9NVPX3NcgoCdI86GUQlmWUt0o0cUyY1/t4rWML0L48Unvhierr3BqcsTj2S9jdM6xDDcnCoU0Q", - "300SByMGyAQWckiPKZK915gwDWUgFoJLsE/F3BbHGM35HKVdu+FcgSTdxdGmYtszZbro+aS5XNej0j5i", - "SM5YLqthzeTx98dzLFFtvIMcbxIPx690dj4snHPtExdjWrHGdhJSGIMJv4UcgjRLKS59/QDEClmqrrku", - "Qos7SQpFd5NIA71sZhZtAMfQySFRigFjofJSOTEiGwso68ZMNA6H9wx5hrYJfBCuJWgNRWMSKZWBzKoQ", - "8LEPjn2oIPfXGyHBjJY/IuBGU1+/anN7Yxk4jqmuufd6jRfINGy4g05HGbjH59yH7Gf0PQThhzJgBzVM", - "Db0erkcbQneEGSAxpvol87fl4eD+myibhJSgs2B56qfjlt2MbJh3s6hzuqDjg9Eo5CbnztnDSpJ6mny4", - "yt4bIQqSv4TdKT2CQiHfsIMx0CQ5EehRwtHeJt+p+s2k4F7dCXi/bx65SqkyGzF2nA9ziPcp/lLkl4A5", - "ABsX95Ea7ewT1LE31uzr9S7kzK4qkFDcP2HsTFJQUTBsd8sL9iaX9+y++bc4a1FTWn+vVDt5K9PRGZhw", - "X9+Sm4Vh9vMwA47V3XIqGuRAhuqtHHO5ucbk/N0qnidTX+VDU3O/inxLVARFSia5IIvVMzzoKcURpkCI", - "cnWgIZMzb+liplQpX96bpGlwQ6UxFU+GAFmQU7IFNFD4wZMISNZFT5xCSn3nk96pJdPQGpFvmv1vWMI9", - "9aLvz9zM0uV3S6WhU4zd9aZMn03gC6bRxP8shNVc726So29QQn6gPRnF8kF3rMYTq11I6401xGFZqusM", - "mVXW1LlIPW1dO9O9jEPRtbafO9ULiPy6uPGC2o6tecFypTXkcY90vCdBtVEaslKhm1fKAr20Tu7eYJCX", - "ZKVaMVXlqgCqF5OmoLG5aik5ik0QedUkUUC0g9HC1Cei44lTujuV7EgZilqrI2rn50CR621WJ1p0RrbM", - "EY9lMD6Lk8cQNR7Cu6f2f5o3L8UW6QZ06sgvmdU1zJlv0a+R7Q8+18A2whgCpaGla1GWGDgutpHltXFc", - "SKN2ROw9R7fKK4G+N90kAiQNV+7OazIrxDzgIk57xOxaq3q1jhJMN3CGJ6+u/YM4HuVHU6N7FEaQuSme", - "sI0y1r80aaR2ya3L2Se5klarsuwqpUhEX3lN+3d8e5bn9oVSlwueX97Hd61UtllpMQ/x1X3nwHYm3Ust", - "1r2AMypnfjhVL7VDVzlPtJMZZI/FHV3YPQLz3WEOeljnfjZcWH9dXWaafsacScat2og8fab+XN52oz5y", - "KRaVzFlGtRUpywQ2w8MeX1aNcwWyyCGaQfJkcbgz5hmBNzIju3H/RQm8Py5bgmc0IxflkLl4KSrLR2W9", - "HgAIKYU+21pTQcZYEmu4ilpRqgQ0kfcBnXiroCfS7WBzI9w5UBZuBdTA+7EB8BNSPswptxx5Ui7UNny/", - "3yafuxHwH/ZTeYd5jLl4XbSkpcnJKySqGeEI6RTXe/2hXmPY+2KqV1RTPHfiDR8BMO4n1YFhkrfUsWAs", - "uSihyFK1F88bHdU8emn70Kx+SXRhPCfPeR1KH7qxaw0+cQqJ+Lpr/6q4IyXVNB9qkmUBW6C4jl9BK6pp", - "OI/sL1BSycOeMkBVWQlX0HEf89lcahQ1xRWEvqbpzAqACq2RfR1Zyi8qvst7ihO/9izyrJmC3aQmhRBL", - "O8UOqEmSSp2tzOiYmKlHyUF0JYqad/BnjhU5umpAd5QTqBq8EbLwjpw6zY80wqswwFnonxJlAibeTeND", - "R7OgNOr2MaCDfpK1GTv1Mu0mGacqagwsOFvRGGKJxFu+YSp+LccVkkOSb59bE/dJKBkh9qst5CjV+PcO", - "FP7FM2Kk8FlPkNolQEGvAtcloW1fg2RSRSUmr7lpniptDsXwA02MjYT0r+kbGJVbb8bb7yzDwZjpJVMb", - "fUjohk5vrp7/XU7i3oM4Ol6KRgz48L89+q9A3f7ZgQ2wlLd0++lkfyzS6G8xz8XnbFGHgcpSXVPNyPgd", - "+hyCHZSoL5iAvFgumms5eG3OfXrPvqpDRP7qG75jSuM/7tX5z5qXYrlDPkPgh27MrLkjIW94JY8A7wXq", - "Jt4vXs0DYEHbosJUtG4xdcxouJ0bJQLaXeShuI9iG34J8TagswPxz9w6xmnqBWou3JXd284hFvziQ4qW", - "DS/ilz4miuyWUQ+pg13v/9nGwsVThfxuVcnzUCHUlyjq8hmsAhyIy65hsz9YcsjXAgk0lYVbotUhur64", - "gcr0SNaVikAYK7/SAXtQcXVQeeZWy5io+e3V2NgTZjppKXe9C1O9bgZAx3UaD4Efl638OPhP5nAdW8YU", - "8P8oeB8pVBvDSzVpPwKWOxk4ErCStnqhtpmGpTnkYELqavec123ujqBiFTLXwA153Jz/4B+ebYpSId1D", - "mHxCG5tmM0oBSyFbZilkVdvEOwYzlcpdhLBY6Y9oHTGhjUkJTpi84uUPV6C1KMY2zp0OKukYl4gIhg7f", - "N6HCaO7U4QDCtG84jM9s1ehxM3eBUxEqctc0lsuC6yJuLiTLQbt7n13znbm5RakxDhyyKfFImulmDYis", - "S0jaBEi580bhW9p7GgD5HRp+Jhhs0C84Yawh1Y5VI/aZIQx/CoPNhm+zUq0winDkQPjctGjhoyegkqgG", - "J/ls2rrDPEb8CvunwbT8nhFZhbNOmWL/uf8BtxKfkT9KYfeefNJR9sM6ye+WDmZAqly1zv9ELMPzmIrE", - "9clX4mjcIGyGUJVAexBtIozYh7p68ZFdRDcIH8YdK8Gnlzvrelqk4n1JM5ChxsDsce8H07qy89y7Zw1V", - "aQNVAyFl7qOlj9S0kX4+3Esj4FFten/Wu9M2LjNunGNqxO2Pj84qVWX5FJ9PqtxReDOBh7QL4wh9REaA", - "kXU37jGmqWXTyXvUKWpzbJm80aI6h6xdVb7v0T+mJhrh6F0ThFoiL6PK7ajdwkieRpky78eYddVgDZNg", - "nGnIa41q4mu+O1x2bCRj9MXfzj579Pjnx599zlwDVogVmDbreK9sV+sXKGRf7/NxPQEHy7PpTQjZBwhx", - "wf4YgqqaTfFnjbitaVOKDoqWHaNfTlwAieOYKBd1o73CcVrX/j/WdqUWeec7lkLBb79nWpVluupDI1cl", - "DCip3YpMKO4FUoE2wljHCLsWUGFbj2izRvUg5v69omwySuYQ9MeeCoQdcblKLWTMoRb5GcZ2e6sRg21V", - "el5Flp596/LvNNLQodCIXjELYJWqvGgvliwFEUYQ6Siy1is+USMe+cg2zJa8ZVOE6D3P06QXF8zez+27", - "xVxtmtO7TUyIF+FQ3oA0x+wT43kLbsJJWtX+H4Z/JBIx3BnXaJb7W/CK5PvgZkX5J4E2DMpPkAcCMBJt", - "24mTjALFokTEmqwEaE8IBuS++PFda1g+GBaCkIQOB8CLw2fbdk0kgwfnd87o+12DlGgp78YoobP8QxG5", - "gfU2F0m0RV5pYi0YYktqKBZG4dbmWRPFPPIqGQQ7a6Uscy/TskwESZMeB89UTDjuSaCvePnxucbXQht7", - "hviA4tV4aFQcKRsjmVBpbpan7wWfNHcUFXt3U8uXGJj9d3B7lLzn/FDeCD+4zVC5gxXrV+FWoFhvdo1j", - "kpPVo8/ZwhfbqDTkwvSN+9dBOGkCQ0GLpXdoha09EIl6aJ0/KXsLMl4GTxz2fWTeamz2HsL2iP7OTGXk", - "5CapPEV9A7JI4C/Fo+LivAeui1sWZrhZ2pcogduRaV+GZYenLo9Sm7hLpzYwXOfk27qD28RF3a5tas6i", - "yfUd3r59YxdTUg2lazG47pjr6E6KMhxVkuE3yHJEOPJj+HlTFPPTWN5byu06kpu7tx+1KA86rHQyrX+Y", - "z1YgwQiDucR/9rVjPu5dGiCgzAvDo0qw3iZdDCEmsdbO5NFUUQ71CenTfbdEzmuMasxrLewO6wYHBZr4", - "OZmP6Zsmt4fPDdPY0vzdZ9UlNLXb20wgtQm36zeKl3gfkYlPultIlSfsK8rw7Q/KX+8t/g0+/cuT4uGn", - "j/5t8ZeHnz3M4clnXzx8yL94wh998ekjePyXz548hEfLz79YPC4eP3m8ePL4yeeffZF/+uTR4snnX/zb", - "PceHHMgEaEjt/3T2f7KzcqWys5fn2WsHbIsTXolvwe0NvpWXCutaOqTmeBJhw0U5exp++l/hhJ3katMO", - "H36d+fpMs7W1lXl6enp9fX0SdzldYeh/ZlWdr0/DPFhtsCOvvDxvfPTJDwd3tNUe46Z6UjjDb6++unjN", - "zl6en7QEM3s6e3jy8OSRL20teSVmT2ef4k94eta476eYX/PU+NT5p1Xlk+cnzXavfMWlLsWFzghsk33d", - "7TYlZfcp8E1c1/q8QNqyw8T9WH8N3bIQwMcPH4Zd8TJPdPWcYhzI0/ezaVXuh5Phzvdzaizq1UsHc0jl", - "0iT388YJjzO0XxLCmv2iJzBfGdSsa3HFLczefZjPqjqBzq8wyMPsw9k8SghP0KiyaDA+wOjL+r8IRj/M", - "Z6eeT86evnd/rYGXmOTJ/bFxhJqHTxp4sfP/N9d8tQJ94tfpfrp6fBok4tP3PnvHh33fTmPvpNP3nSQn", - "xYGewfvmUJPT96F88/4BO6V7vd9j1GEioPuanS6wZNPUphCvbnwpSPPm9D0+Bkd/P/UavfRHfJQTtz8N", - "yYJGWlJaiPTHDgrf261byP7hXJtovJzbfF1Xp+/xP0i2H+i0l5DKKkTlIjhrm8+ZsIwvlMZqwDZfO24Q", - "ypAKE7UcHPkz1+sZQRCquqOry+zpm2EsEg7Ewkh4Xbq7oL3NOjO1Aguq9iOm0IhjnfatUPbmYfbFu/eP", - "5o8efvgXJ3T5Pz/79MNET+5nzbjsopGoJjZ8d0uON9AftIukTWoY2FDg9bQwHmvit6o3EGuQcaDWYG/4", - "odyODPjJHfL4bg7cBH//khcshOzj3I8+3tznkvyVndBEwt2H+eyzj7n6c+lInpcMW0bFo4db/6O8lOpa", - "hpZOEq83G6534RibDlNgfrNR3uvdgPOZVDJK7CdXJGaoVNqEEX5jLL8Bv7lwvf6b33QaDixOGBNGmj9f", - "aDzyMaHLpKmrBiHbafBz58UVl3kIDGo99XG/SPL2hNE4g9YGlnUZUmJUpVhS/XalyjCRqavKcZwlNw1l", - "+fAA93ijiP5maFbLXEly48FIjGCMxMh8NGiaS1F1uoiloypfWZyigk7Cpv+zBr1rd30j3Cus3d6Bo9lv", - "ycIJj3fAwrsD3TELf3wkG/3zr/i/9qX15OFfPh4EIZHOa7EBVds/66V5QTfYrS5NL8NTLYhTu5Wn6Gp8", - "+r7zXPGfB8+V7u9t97jF1UYVEJ4Qark0qFrZ9/n0Pf0bTQTbCrTYgKQS8f5XujlOsUD5bvjzTubJH4fr", - "6OQIHvn5NGj3Uq/kbsv3nT+7Lz+zrm2hrqmmZlJeweuTl2zDJV9RQHmjEHP3oB+gTV/Mfqiai8rHkTKO", - "peBUbVuNJYVV+ODyxqaMN1rjWbQSEidA4yDOwpeuK48ucF+N8WQoG3nIvlcFDGWj1EXoYexchs1RSNU9", - "vO3FOGS8H447KGjEJAv8kIzcx9r0/z695sI6CcrnEUaMDjtb4OWpLxrW+7Wt0zH4gsVHoh/jCPnkr6e8", - "ey66GhS3ZWMdB+qV1FevQRhpFAI7wufWkBAr5pFcGpX8m3du1w3oq0BJrZ756ekpRvqtlbGnKIl2ddDx", - "x3fNRodyyM2Gu2/bTGmxEpKXmVeStZUPZ49PHs4+/P8AAAD//50s/LmpDQEA", + "H4sIAAAAAAAC/+y9e5PbtpI4+lVQ2q3yY8UZ23GyJ751au/ETnLmxklcHid7d23fE4hsSThDATwAOCPF", + "19/9V+gGSJAEJWpm4iS1+5c9Ih6NRqPR6OeHWa42lZIgrZk9+zCruOYbsKDxL57nqpY2E4X7qwCTa1FZ", + "oeTsWfjGjNVCrmbzmXC/VtyuZ/OZ5Bto27j+85mGf9ZCQzF7ZnUN85nJ17DhbmC7q1zrZqRttlKZH+KM", + "hjh/Mfu45wMvCg3GDKH8UZY7JmRe1gUwq7k0PHefDLsWds3sWhjmOzMhmZLA1JLZdacxWwooC3MSFvnP", + "GvQuWqWffHxJH1sQM61KGML5XG0WQkKAChqgmg1hVrEClthozS1zMzhYQ0OrmAGu8zVbKn0AVAIihhdk", + "vZk9ezszIAvQuFs5iCv871ID/AqZ5XoFdvZ+nlrc0oLOrNgklnbusa/B1KU1DNviGlfiCiRzvU7Y97Wx", + "bAGMS/b6m+fss88++9ItZMOthcIT2eiq2tnjNVH32bNZwS2Ez0Na4+VKaS6LrGn/+pvnOP+FX+DUVtwY", + "SB+WM/eFnb8YW0DomCAhIS2scB861O96JA5F+/MClkrDxD2hxne6KfH8v+uu5Nzm60oJaRP7wvAro89J", + "HhZ138fDGgA67SuHKe0Gffso+/L9h8fzx48+/svbs+y//Z+ff/Zx4vKfN+MewECyYV5rDTLfZSsNHE/L", + "msshPl57ejBrVZcFW/Mr3Hy+QVbv+zLXl1jnFS9rRyci1+qsXCnDuCejApa8Li0LE7Nalo5NudE8tTNh", + "WKXVlSigmDvue70W+Zrl3NAQ2I5di7J0NFgbKMZoLb26PYfpY4wSB9eN8IEL+uMio13XAUzAFrlBlpfK", + "QGbVgesp3DhcFiy+UNq7yhx3WbE3a2A4uftAly3iTjqaLssds7ivBeOGcRaupjkTS7ZTNbvGzSnFJfb3", + "q3FY2zCHNNyczj3qDu8Y+gbISCBvoVQJXCLywrkbokwuxarWYNj1Guza33kaTKWkAaYW/4Dcum3/fy5+", + "/IEpzb4HY/gKXvH8koHMVQHFCTtfMqlsRBqelhCHrufYOjxcqUv+H0Y5mtiYVcXzy/SNXoqNSKzqe74V", + "m3rDZL1ZgHZbGq4Qq5gGW2s5BhCNeIAUN3w7nPSNrmWO+99O25HlHLUJU5V8hwjb8O1fH809OIbxsmQV", + "yELIFbNbOSrHubkPg5dpVctigphj3Z5GF6upIBdLAQVrRtkDiZ/mEDxCHgdPK3xF4IRBRsFpZjkAjoRt", + "gmbc6XZfWMVXEJHMCfvJMzf8atUlyIbQ2WKHnyoNV0LVpuk0AiNOvV8Cl8pCVmlYigSNXXh0OAZDbTwH", + "3ngZKFfSciGhcMwZgVYWiFmNwhRNuP+9M7zFF9zAF0/H7vj268TdX6r+ru/d8Um7jY0yOpKJq9N99Qc2", + "LVl1+k94H8ZzG7HK6OfBRorVG3fbLEWJN9E/3P4FNNQGmUAHEeFuMmIlua01PHsnH7q/WMYuLJcF14X7", + "ZUM/fV+XVlyIlfuppJ9eqpXIL8RqBJkNrMkHF3bb0D9uvDQ7ttvku+KlUpd1FS8o7zxcFzt2/mJsk2nM", + "YwnzrHntxg+PN9vwGDm2h902GzkC5CjuKu4aXsJOg4OW50v8Z7tEeuJL/av7p6pK19tWyxRqHR37KxnV", + "B16tcFZVpci5Q+Jr/9l9dUwA6CHB2xaneKE++xCBWGlVgbaCBuVVlZUq52VmLLc40r9qWM6ezf7ltNW/", + "nFJ3cxpN/tL1usBOTmQlMSjjVXXEGK+c6GP2MAvHoPETsglieyg0CUmb6EhJOBZcwhWX9qR9snT4QXOA", + "3/qZWnyTtEP47j3BRhHOqOECDEnA1PCeYRHqGaKVIVpRIF2VatH8cP+sqloM4vezqiJ8oPQIAgUz2Apj", + "zQNcPm9PUjzP+YsT9m08NoriSpY7dzmQqOHuhqW/tfwt1uiW/BraEe8Zhtup9InbmoAGJ+bfBcXhs2Kt", + "Sif1HKQV1/hvvm1MZu73SZ3/HCQW43acuPCh5TFHbxz8JXrc3O9RzpBwvLrnhJ31+96MbNwoewjGnLdY", + "vGviwV+EhY05SAkRRBE1+e3hWvPdzAuJGQp7QzL5yQBRSMVXQiK0c/d8kmzDL2k/FOLdEQKY5l1EtEQS", + "ZKNC9TKnR/3JQM/yJ6DW1MYGSdRJqqUwFt/V2JitoUTBmctA0DGp3IgyJmz4nkU0MF9rXhEt+y8kdgmJ", + "73lqRLDe8uKdeCcmYY7YfbTRCNWN2fJB1pmEBLlGD4avSpVf/o2b9R2c8EUYa0j7OA1bAy9AszU368TB", + "6dF2O9oU+nYNkWbZIprqpFniS7Uyd7DEUh3DuqrqOS9LN/WQZfVWiwNPOshlyVxjBhuBCnP/cCQNO72/", + "2Nc8XzuxgOW8LOetqkhVWQlXULpHu5AS9JzZNbft4ceRw7sGz5EBx+wssGg1Xs2EKjbd6CI0sA3HG2jj", + "XjNV2e3TcFDDN9CTgvBGVDVqEaKHxvmLsDq4Aok8qRkawW/WiNqaePATN7f/hDNLRYsjDaAN5rsGfw2/", + "6ADtWrf3qWynULognbV1vwnNcqVpCLrh/eTuP8B125mo836lIfNDaH4F2vDSra63qAcN+d7V6TxwMgtu", + "eXQyPRWmH2DEObAfinegE1qaH/E/vGTus5NiHCW11CNQGFGRObWgi9mhimZyDVDfqtiGVJms4vnlUVA+", + "bydPs5lJJ+9r0p76LfSLaHbozVYU5q62CQcb26vuCSHdVWBHA1lkL9OJ5pqCgDeqYsQ+eiAQp8DRCCFq", + "e+fX2ldqm4LpK7UdXGlqC3eyE26cycz+K7V94SFT+jDmcewpSHcLlHwDBm83GTNON0trlztbKH0zaaJ3", + "wUjWWhsZd6NGwtS8hyRsWleZP5sJiwU16A3UOnjsFwL6w6cw1sHCheW/ARaMG/UusNAd6K6xoDaVKOEO", + "SH+dFOIW3MBnT9jF384+f/zk708+/8KRZKXVSvMNW+wsGHbfq+WYsbsSHiRfRyhdpEf/4mmwUXXHTY1j", + "VK1z2PBqOBTZvuj1S82YazfEWhfNuOoGwEkcEdzVRmhnZNZ1oL2ARb26AGvdS/eVVss754aDGVLQYaNX", + "lXaChenaCb20dFq4JqewtZqfVtgSZEF+Bm4dwrg34GZxJ0Q1tvFFO0vBPEYLOHgojt2mdppdvFV6p+u7", + "UG+A1konr+BKK6tyVWZOzhMqoaB45Vsw3yJsV9X/naBl19wwNzdaL2tZjOgh7FZOv79o6Ddb2eJm7w1G", + "602szs87ZV+6yG9fIRXozG4lQ+rsqEeWWm0YZwV2RFnjW7Akf4kNXFi+qX5cLu9G26lwoIQeR2zAuJkY", + "tXDSj4FcSXLmO6Cy8aNOQU8fMcHKZMcB8Bi52MkcTWV3cWzHtVkbIdFub3Yyj1RbDsYSilWHLG+vwhpD", + "B011zyTAceh4iZ9RV/8CSsu/UfpNK75+q1Vd3Tl77s85dTncL8ZbAwrXN6iBhVyVXQfSlYP9JLXG32VB", + "zxslAq0BoUeKfClWaxu9F19p9RvciclZUoDiB1IWla7PUGX0gyocM7G1uQNRsh2s5XCObmO+xheqtowz", + "qQrAza9NWsgccTlEXyd00bKx3Ir6CWHYAhx15bx2q60rhg5Ig/ui7ZjxnE5ohqgxI+4Xjd8MtaLpyJ2t", + "1MCLHVsASKYW3sfBe1/gIjl6T9kgpnkRN8EvOnBVWuVgDBSZV0UfBC20o6vD7sETAo4AN7Mwo9iS61sD", + "e3l1EM5L2GXo62fY/e9+Ng9+B3itsrw8gFhsk0JvX582hHra9PsIrj95THakqSOqdeKtYxAlWBhD4VE4", + "Gd2/PkSDXbw9Wq5Ao0vJb0rxYZLbEVAD6m9M77eFtq5GPNj9M91JeG7DJJcqCFapwUpubHaILbtGHV2C", + "W0HECVOcGAceEbxecmPJDUrIAnWadJ3gPCSEuSnGAR59hriRfw4vkOHYubsHpalN8xwxdVUpbaFIrQEt", + "sqNz/QDbZi61jMZu3jxWsdrAoZHHsBSN75HlX8D4B7eN/dVbdIeLQ5u6u+d3SVR2gGgRsQ+Qi9Aqwm7s", + "xTsCiDAtoolwhOlRTuM6PJ8Zq6rKcQub1bLpN4amC2p9Zn9q2w6Ji4wcdG8XCgwaUHx7D/k1YZb8t9fc", + "MA9HMLGjOof8tYYwu8OYGSFzyPZRPj7xXKv4CBw8pHW10ryArICS7xLOAfSZ0ed9A+COt89dZSEjR9z0", + "preUHPwe9wytcDyTEh4ZfmG5O4LuKdASiO99YOQCcOwUc/J0dK8ZCudKblEYD5dNW50YEW/DK2Xdjnt6", + "QJA9R58C8AgemqFvjgrsnLVvz/4U/wXGT9DIEcdPsgMztoR2/KMWMKIL9jFO0XnpsfceB06yzVE2doCP", + "jB3ZEcX0K66tyEWFb53vYHfnT7/+BEnDOSvAclFCwaIP9Ays4v6MXEj7Y97sKThJ9zYEf6B8SywnuOl0", + "gb+EHb65X1FsQqTquIu3bGJUdz9xyRDQ4PHsRPC4CWx5bsudE9TsGnbsGjQwUy/IhWFoT7GqyuIBkvaZ", + "PTN662zSNrrXXHyBQ0XLS/ma0ZtgP3xveg+DDjr8W6BSqpygIRsgIwnBJN8RVim368KHP4UAmEBJHSA9", + "00bTfHP93zMdNOMK2H+pmuVc4pOrttDINEqjoIACpJvBiWDNnN45scUQlLABeknil4cP+wt/+NDvuTBs", + "CdchZtA17KPj4UPU47xSxnYO1x3oQ91xO09cH2i4cheff4X0ecphjyc/8pSdfNUbvLF2uTNljCdct/xb", + "M4DeydxOWXtMI9O8vXDcSbacrn/QYN247xdiU5fc3oXVCq54makr0FoUcJCT+4mFkl9f8fLHphvGQ0Lu", + "aDSHLMcovoljwRvXhwL/3DhCCneAyel/KkBwTr0uqNOBJ2brqSo2GygEt1DuWKUhB4p3c5KjaZZ6wsgT", + "Pl9zucIHg1b1yju30jjI8GtDqhldy8EQSaHKbmWGSu7UBeDd1ELIoxOngLsnXV9DTg+Ya97M56Ncp9zM", + "0R70LQZJI9l8NvridUi9al+8hJxu3OaEy6Aj70X4aSeeaEpB1DnZZ4iveFvcYXKb+9uo7NuhU1AOJ448", + "ftuPY06/7rld7u5A6KGBmIZKg8ErKlZTGfqqlnGMdnAV3BkLm6Emn7r+feT4vR59LypZCgnZRknYJdOS", + "CAnf48fkccJrcqQzCixjfftvkA78PbC680yhxtviF3e7f0L7FivzjdJ3ZRKlASeL9xMskAfN7X7Km9pJ", + "eVkmTIs+grPPAMy8cdYVmnFjVC5QZjsvzNx7BZM10od7dtH/qolLuYOz1x+3Z0OLkwOgjhjKinGWlwI1", + "yEoaq+vcvpMcdVTRUhNOXOExPq61fB6apNWkCS2mH+qd5OjA12iukg4bS0ioab4BCMpLU69WYGzvrbME", + "eCd9KyFZLYXFuTbuuGR0XirQ6El1Qi03fMeWjiasYr+CVmxR2670jwHKxoqy9AY9Nw1Ty3eSW1YCN5Z9", + "L+SbLQ4XjP7hyEqw10pfNlhI3+4rkGCEydLOZt/SV/Tr98tfex9/dHenz8HptM2YMHPL7CRJ+f/u/8ez", + "t2fZf/Ps10fZl/92+v7D048PHg5+fPLxr3/9/7s/ffbxrw/+419TOxVgT4XPesjPX/iX8fkLfP5Ervp9", + "2D+Z/n8jZJYkstibo0db7D6mivAE9KCrHLNreCftVjpCuuKlKBxvuQk59G+YwVmk09Gjms5G9JRhYa1H", + "PipuwWVYgsn0WOONpaihf2Y6UB2Nkj72HM/Lspa0lUH6pjjM4F+mlvMmGQHlKXvGMFJ9zYOTp//zyedf", + "zOZthHnzfTaf+a/vE5Qsim0qj0AB29RbMQ6SuGdYxXcGbJp7IOxJVzry7YiH3cBmAdqsRfXpOYWxYpHm", + "cCFkyeuctvJckoO/Oz9o4tx5y4lafnq4rQYooLLrVP6ijqCGrdrdBOi5nVRaXYGcM3ECJ32dT+Hei96p", + "rwS+DI6pWqkpr6HmHBChBaqIsB4vZJJiJUU/vfAGf/mbO38O+YFTcPXnTHn03vv26zfs1DNMc49SWtDQ", + "URKCxFPaB092HJIcN4tjyt7Jd/IFLFH7oOSzd7Lglp8uuBG5Oa0N6K94yWUOJyvFnoV4zBfc8ndyIGmN", + "JlaMgqZZVS9KkbPL+EHSkiclyxqO8O7dW16u1Lt37we+GcPng58qyV9ogswJwqq2mU/1k2m45jpl+zJN", + "qhccmXJ57ZuVhGxVk4I0pBLy46d5Hq8q00/5MFx+VZVu+REZGp/QwG0ZM1Y18WhOQPEhvW5/f1D+YtD8", + "OuhVagOG/bLh1Vsh7XuWvasfPfoMI/vaHAi/+Cvf0eSugsnaldGUFH2lCi6cnpXoq55VfJUysb1799YC", + "r3D3UV7eoI6jLBl260QdhgADHKpdQBPiPLoBBMfRwcG4uAvqFdI6ppeAn3ALuwHYt9qvKH7+xtt1IAaf", + "13adubOdXJVxJB52psn2tnJCVvDGMGKFr1WfGG8BLF9DfukzlsGmsrt5p3tw+PGCZmAdwlAuO4owxGxK", + "aKBYAKurgntRnMtdP62NoYgKHPQ1XMLujWqTMR2Tx6abVsWMHVSk1Ei6dMQaH1s/Rn/zvVdZCDT12Ukw", + "eDOQxbOGLkKf8YNMIu8dHOIUUXTSfowhgusEIoj4R1Bwg4W68W5F+qnlCZmDtOIKMijFSixSaXj/c2gP", + "C7A6qvSZB70XcjOgYWLJ3FN+QRerf95rLlfgrmd3pSrDS8qqmnTawPfQGri2C+B2r55fxgkpAnT4pLzG", + "yGvU8M3dEmDr9ltY1NhJuHavClQUURvvvXwy7n9GgENxQ3hC9/alcDL61vWoS2QcDLdyg93mWetd82I6", + "Q7jo+wYwZam6dvvioFA+2yYldYnul9rwFYy8XWLr3cR8GB2LHw5ySCJJyiBq2Rc1BpJAEmRqnLk1J88w", + "uC/uEOMzs+eQGWYiA7G3GWESbY+wRYkCbOO5SnvPdceKSlmBx0BLsxbQshUFAxhdjMTHcc1NOI6YLzVw", + "2UnS2W+Y9mVfarrzyJcwSoraJJ4Lt2Gfgw7e/T5BXchKF1LRxY/+CWnl3NsLwxdS26EkiqYFlLCihVPj", + "QChtwqR2gxwcPy6XyFuylFtipKCOBAA/B7iXy0PGyDbCJo+QIuMIbHR8wIHZDyo+m3J1DJDSJ3ziYWy8", + "IqK/IR3YR476ThhVlbtcxYi9MQ8cwKeiaCWLnkc1DsOEnDPH5q546dicf4u3gwwypOGDopcPzbvePBh7", + "aOwxTdGVf9SaSEi4yWpiaTYAnRa190C8UNuMIpSTb5HFduHoPRm7gPHSqYNJuejuGbZQW3TnwquFfOUP", + "wDIORwAj0r1shUF6xX5jchYBs2/a/XJuigoNkoxXtDbkMiboTZl6RLYcI5f7UXq5GwHQU0O1tRq8WuKg", + "+qArngwv8/ZWm7dpU0NYWOr4jx2h5C6N4G+oH+smhPtbm/hvPLlYOFGfJBPeULN0mwyF1LmirIPHJCjs", + "k0MHiD1YfdWXA5No7fp6dfEaYS3FShzzHRolh2gzUAI+grOOaJpdpjwF3Fse8B6/CN0iZR3uHpe7B5ED", + "oYaVMBZao1HwC/o91PEc0ycrtRxfna300q3vtVLN5U9mc+zYWeYnXwF64C+FNjZDi1tyCa7RNwaVSN+4", + "pmkJtOuiSMUGRJHmuDjtJeyyQpR1ml79vN+9cNP+0Fw0pl7gLSYkOWgtsDhG0nF5z9Tk2753wS9pwS/5", + "na132mlwTd3E2pFLd44/ybnoMbB97CBBgCniGO7aKEr3MMgo4HzIHSNpNPJpOdlnbRgcpiKMfdBLLYS9", + "j938NFJyLVEawHSEoFqtoAjpzYI9TEZJ5EolV1EVp6ralzPvhFHqOsw8tydpnXfDhzEn/Ejcz4QsYJuG", + "Pn4VIORtZB0m3MNJViApXUlaLZRETezijy0iXd0ntoX2AwCSTtBvesbs1juZdqnZTtyAEnjh3yQGwvr2", + "H8vhhnjUzcfcpzuZT/cfIRwQaUrYqLDJMA3BCAPmVSWKbc/wRKOOKsH4UdrlEWkLWYsf7AAGuk7QSYLr", + "pNL2rtZewX6Kb95T9yoj32vvWOzom+c+AL+oNVowOp7Nw7ztzVtt4tq/+/nCKs1X4K1QGYF0qyFwOceg", + "IcqKbpgV5E5SiOUSYuuLuYnloAPcQMdeTCDdBJGlTTS1kPaLpykyOkA9LYyHUZammAQtjNnk3wytXEGm", + "j1RJzZUQbc0NTFXJcP3vYJf9zMvaPTKENq17rjc7dS/fI3b9avMd7HDkg16vDrADu4Kap9eANJjS9Def", + "TJTA+p7ppPjH52VnC4/YqbP0Lt3R1viiDOPE394ynaIF3aXc5mC0ThIOlim7cZH2TXCnB7qI75PyoU0Q", + "xWEZJJL346mECSUsh1dRk4viEO2+AV4G4sXlzD7OZ7fzBEjdZn7EA7h+1VygSTyjpylZhjuOPUeinFeV", + "Vle8zLy/xNjlr9WVv/yxeXCv+MQvmTRlv/n67OUrD/7H+Swvgeus0QSMrgrbVX+aVVEZh/1XCWX79opO", + "0hRFm99kZI59LK4xs3dP2TQoitL6z0RH0ftcLNMO7wd5n3f1oSXucfmBqvH4aW2e5PDTdfLhV1yUwdgY", + "oB1xTsfFTausk+QK8QC3dhaKfL6yO2U3g9OdPh0tdR3gSTjXj5iaMv3ikD5xJbIi7/zD71x6+kbpDvP3", + "kYlJ56HfTqxyQjbhccRXO9Sv7AtTJ4wEr19Wv7jT+PBhfNQePpyzX0r/IQIQf1/43/F98fBh0nqYVGM5", + "JoFaKsk38KCJshjdiE/7AJdwPe2CPrvaNJKlGifDhkLJCyig+9pj71oLj8/C/1JACe6nkymP9HjTCd0x", + "MFNO0MVYJGLjZLqhkpmGKdn3qcYgWEdayOx9SQYyxg6PkKw3aMDMTCnytGuHXBjHXiU5U7rGDBuPaGvd", + "iLUY8c2VtYjGcs2m5EztARnNkUSmSaZtbXG3UP5411L8swYmCveqWQrQeK/1rrrwOMBRBwJpWi/mByY7", + "VTv8bfQge+xNQRe0Twmy1373orEphYWmiv4c6QEezzhg3Hu8tz19eGqmaLZ11wVz2jtmSun0wOi8sW5k", + "jmQpdGGypVa/QtoQgvajRCKMYPgUqOb9FWTKc6/PUhqjclvRvZ390HZPfxuPbfyt38Jh0U3VsZtcpulT", + "fdxG3uTRa9Lpmj2Sxx5hsYdBNzRghLXg8YqcYbEMSvA+4pLOE2WB6ESYpU9lHMt5SuO3p9LDPIh/Lfn1", + "gqdqxLi3kIMp2t6On5RVLHQOG2CaHAc0O4s8uJu2gjLJVaBbG8QwK+0N3zU07eQXTfuAQYqKny5zclMo", + "jUoMU8trLqmKuOtH/Mr3NkAmeNfrWmnMA2nSLl0F5GKTVMe+e/e2yIfuO4VYCSqQXRuIKjD7gRglm0Qq", + "8lWsm8wdHjXnS/ZoHpWB97tRiCthxKIEbPGYWiy4weuyMYc3XdzyQNq1weZPJjRf17LQUNi1IcQaxZq3", + "Jwp5jWPiAuw1gGSPsN3jL9l9dMk04goeOCx6IWj27PGX6FBDfzxK3bK+wPk+ll0gzw7O2mk6Rp9UGsMx", + "ST9q2vt6qQF+hfHbYc9poq5TzhK29BfK4bO04ZKvIB2fsTkAE/XF3URzfg8vkqwBYKxWOyZsen6w3PGn", + "kZhvx/4IDJarzUbYjXfcM2rj6Kktr0yThuGo1r+vFxXgCh/R/7UK7n89XdcnfsbwzUjMFnop/4A22hit", + "c8Yp+WcpWs/0UK+TnYfcwlhAq6mbRbhxc7mloyyJjupLVmkhLeo/arvM/uKexZrnjv2djIGbLb54mihE", + "1a3VIo8D/JPjXYMBfZVGvR4h+yCz+L7svlQy2ziOUjxocyxEp3LUUTftkjnmF7p/6KmSrxslGyW3ukNu", + "POLUtyI8uWfAW5Jis56j6PHolX1yyqx1mjx47Xbop9cvvZSxUTpVMKA97l7i0GC1gCuMmEtvkhvzlnuh", + "y0m7cBvof1//pyByRmJZOMvJh0Bk0dwXLO+k+J+/bzOfo2GVIhF7OkClE9pOr7f7xN6Gx2nd+vZbchjD", + "byOYm4w2HGWIlRHve3Kvb/r8Hv5CfZBozzsKx8e/MO3e4CjHP3yIQD98OPdi8C9Pup+JvT98mE5AnFS5", + "uV9bLNzmRYx9U3v4lUoowELVwsahyOdHSCggxy4p98ExwYUfas66FeI+vRRxN/FdaW/T9Cl49+4tfgl4", + "wD/6iPidmSVuYBulMH7YuxUykyRTNN8jP3fOvlLbqYTTu4MC8fwBUDSCkonqOVzJoAJo0lx/0F8kolE3", + "6gJK5R6ZcVGgWJ//58GzW/x8D7ZrURY/t7ndeheJ5jJfJ72EF67j30lG71zBxCqTdUbWXEook8PR2/bv", + "4Q2ceKX/Q02dZyPkxLb9CrS03N7iWsC7YAagwoQOvcKWboIYq920WU1ahnKlCobztEUtWuY4LOWcKqGZ", + "iG/GYTe19X6rGAvuEw4tRYlumGm7MbbMNLcjCbSw3nmoL+TGwfLjhtQMNDpoxsUGL2bDN1UJeDKvQPMV", + "dlUSet0xhRqOHFWsYKZyn7AlJqxQzNZaMrVcRssAaYWGcjdnFTeGBnnklgVbnHv27PGjR0m1F2JnwkoJ", + "i2GZP7ZLeXyKTeiLL7JEpQCOAvYwrB9bijpmY4eE42tK/rMGY1M8FT9Q5CpaSd2tTfUkm9qnJ+xbzHzk", + "iLiT6h7VlSGJcDehZl2VihdzTG785uuzl4xmpT5UQp7qWa5QW9cl/6R5ZXqC0ZDZaSRzzvRx9qfycKs2", + "NmvKT6ZyE7oWbYFM0fO5QT1ejJ0T9oJUqE0Bf5qEYYpsvYEiqnZJj3gkDvcfa3m+Rt1kRwIa55XTC7EG", + "dtZabqLow6b6ETJsB7evxUqlWOdM2TXoa2EAI/LhCrrpEJvcoF43HtIjdpenaymJUk6OEEabWkfHoj0A", + "R5JscCpIQtZD/JGaKarHfGxd2gvslY7F6BW57Vn9Q3K9kGKbfe+NCzmXSoocSyGkJGlM3TbNTDmhakTa", + "vmhm/oQmDleytG4TC+yxOFpsNzBCj7ihyT/66jaVqIP+tLD1JddWYI3nbFDMQ6VrbxAT0oCvZuWIKOaT", + "SiecmpKBEI0DxZFkhFmZRjSc37hvP3j9NybFuBQSNV0ebf59Riar0gi0TEsmLFspMH493Wge89b1OcEs", + "jQVs35+8VCuRX4gVjkFudG7Z5DM6HOoseJB6j03X9rlr63PnNz933MFo0rOq8pOO10FPCpJ2K0cRnPJb", + "Co4kEXKb8ePR9pDbXtdvvE8docEVeq1BhffwgDCaWtrdUb52b0uiKGzBKKIymUBXyAQYL4UMJtT0BZEn", + "rwTcGDyvI/1Mrrmlt8MknvYGeDkSAIERymSDv+1Q/coBDiW4xjDH+Da2ZcBHGEfToJX4udyxcCgcdUfC", + "xHNeNq7TiaLeKFV5IarA4KJeme8U43CMOwshkx10HQzfa7pjNY5jb6KxHIWLuliBzXhRpFJbfYVfGX4N", + "QWKwhbxuilA10YHdHOVDavMT5UqaerNnrtDgltNFdfMT1BDX7g87jJl2Fjv8N1WBaXxnvNP00VG5wUO6", + "OC4x/zDKOCX1OprOjFhl0zGBd8rt0dFOfTNCb/vfKaWHcN0/RDRuj8vFe5Tib1+7iyNO3DvwT6erpcmr", + "i77gCr+HhEdNRsguV8KrbFBnDL0ecPMSW9YDPjRMAn7Fy5FI+NhWQvcr2Q/G4uHz0fQN3Pr0XJazvSxo", + "NOUR+Qr3rC9DE+KYfzC5B9+d1cKvdS9Cx21333UsdeQj1jKLUQvdzYxo7QYfa0X77mosRUKo04Hf43og", + "3otn7tPAw5VQdfC+Cj7Q4UlIv/oUPJ26HyPrT0YW/N5Wi1Ebyxtfv5aW6d/k3/1MVlgG0urdH8DiMtj0", + "flGZhLRL6qm2CWtKH04qhdi5FafUsEmVS/GyYdCVEWvp0NKg/MyArF5MEQcG+Pg4n50XR12YqZI7Mxol", + "dexeitXaYsb+vwEvQL86UJGgrUKAR6xSRrQVSEs3mE8Bu8bhTqYGGzgCFnFFheFYwQn1CnKLZWdb5zoN", + "cEx9BTdZMPr8b2WC8ed0E5PhCxLsq0IwrDV74I4fJE6Kkn9Rnc6T6Tn3zxoXaooAu+amTdfSi5meHLm5", + "XEKOWZH3Jqr6zzXIKAnSPOhlEJZllLdKNHFMmNf7eK1jC9C+PFJ74Ynq69wanLE49kvY3TOsQw3JwqFN", + "EN9NEgcjBsgEFnJIjymSvdeYMA1lIBaCS7BPxdwWxxjN+RylXbvhXIEk3cXRpmLbM2W66PmkuVzXo9I+", + "YkjOWC6rYc3k8ffHCyxRbbyDHG8SD8evdHY+LJxz7RMXY1qxxnYSUhiDCb+FHII0Sykuff0AxApZqq65", + "LkKLO0kKRXeTSAO9bGYWbQDH0MkhUYoBY6HyUjkxIhsLKOvGTDQOh/cMeYa2CXwQriVoDUVjEimVgcyq", + "EPCxD459qCD31xshwYyWPyLgRlNfv25ze2MZOI6prrn3eo0XyDRsuINORxm4x+fch+zn9D0E4YcyYAc1", + "TA29Hq5HG0J3hBkgMab6JfO35eHg/psom4SUoLNgeeqn45bdjGyYd7Ooc7qg44PRKOQm587Zw0qSepp8", + "uMreGyEKkr+E3Sk9gkIh37CDMdAkORHoUcLR3ibfqfrNpOBe3Ql4v28euUqpMhsxdpwPc4j3Kf5S5JeA", + "OQAbF/eRGu3sPurYG2v29XoXcmZXFUgoHpwwdiYpqCgYtrvlBXuTy3t23/xbnLWoKa2/V6qdvJPp6AxM", + "uK9vyc3CMPt5mAHH6m45FQ1yIEP1Vo653Fxjcv5uFc+Tqa/yoam5X0W+JSqCIiWTXJDF6jke9JTiCFMg", + "RLk60JDJmbd0MVOqlC/vTdI0uKHSmIonQ4AsyCnZAhoo/OBJBCTroidOIaW+80nv1JJpaI3IN83+Nyzh", + "nnrR92duZunyu6XS0CnG7npTps8m8AXTaOJ/FsJqrnc3ydE3KCE/0J6MYvmgO1bjidUupPXGGuKwLNV1", + "hswqa+pcpJ62rp3pXsah6Frbz53qBUR+Xdx4QW3H1rxgudIa8rhHOt6ToNooDVmp0M0rZYFeWid3bzDI", + "S7JSrZiqclUA1YtJU9DYXLWUHMUmiLxqkigg2sFoYeoT0fHEKd2dSnakDEWt1RG183OgyPU2qxMtOiNb", + "5ojHMhifxcljiBoP4d1T+z/Nm5dii3QDOnXkl8zqGubMt+jXyPYHn2tgG2EMgdLQ0rUoSwwcF9vI8to4", + "LqRROyL2nqNb5ZVA35tuEgGShit35zWZFWIecBGnPWJ2rVW9WkcJphs4w5NX1/5BHI/yk6nRPQojyNwU", + "T9lGGetfmjRSu+TW5ex+rqTVqiy7SikS0Vde0/49357luX2p1OWC55cP8F0rlW1WWsxDfHXfObCdSfdS", + "i3Uv4IzKmR9O1Uvt0FXOE+1kBtljcUcXdo/AfH+Ygx7WuZ8NF9ZfV5eZpp8xZ5JxqzYiT5+pP5e33aiP", + "XIpFJXOWUW1FyjKBzfCwx5dV41yBLHKIZpA8WRzujHlG4I3MyG7cf1EC74/LluAZzchFOWQuXorK8lFZ", + "rwcAQkqhz7bWVJAxlsQarqJWlCoBTeR9QCfeKuiJdDvY3Ah3DpSFWwE18H5sALxPyoc55ZYjT8qF2obv", + "D9rkczcC/uN+Ku8wjzEXr4uWtDQ5eYVENSMcIZ3ieq8/1BsMe19M9YpqiudOvOEjAMb9pDowTPKWOhaM", + "JRclFFmq9uJ5o6OaRy9tH5rVL4kujOfkOa9D6UM3dq3BJ04hEV937V8Vd6SkmuZDTbIsYAsU1/EraEU1", + "DeeR/QVKKnnYUwaoKivhCjruYz6bS42ipriC0Nc0nVkBUKE1sq8jS/lFxXd5T3Hi155FnjVTsJvUpBBi", + "aafYATVJUqmzlRkdEzP1KDmIrkRR8w7+zLEiR1cN6I5yAlWDN0IW3pFTp/mJRngdBjgL/VOiTMDE+2l8", + "6GgWlEbdPgZ00E+yNmOnXqbdJONURY2BBWcrGkMskXjLN0zFr+W4QnJI8u1za+I+CSUjxH69hRylGv/e", + "gcK/eEaMFD7rCVK7BCjoVeC6JLTta5BMqqjE5DU3zVOlzaEYfqCJsZGQ/jV9A6Ny6814+51lOBgzvWRq", + "ow8J3dDpzdXzv8tJ3HsQR8dL0YgBH/63R/8VqNs/O7ABlvKWbj+d7I9FGv0t5rn4nC3qMFBZqmuqGRm/", + "Q19AsIMS9QUTkBfLRXMtB6/NuU/v2Vd1iMhffcN3TGn8x706/1nzUix3yGcI/NCNmTV3JOQNr+QR4L1A", + "3cT7xat5ACxoW1SYitYtpo4ZDbdzo0RAu4s8FPdRbMMvId4GdHYg/plbxzhNvUDNhbuye9s5xIJffEjR", + "suFF/NLHRJHdMuohdbDr/X+1sXDxVCG/W1XyPFQI9SWKunwGqwAH4rJr2OwPlhzytUACTWXhlmh1iK4v", + "bqAyPZJ1pSIQxsqvdMAeVFwdVJ651TIman57NTb2hJlOWspd78JUr5sB0HGdxkPgx2UrPw3+kzlcx5Yx", + "Bfw/Ct5HCtXG8FJN2k+A5U4GjgSspK1eqG2mYWkOOZiQuto953WbuyOoWIXMNXBDHjfnP/qHZ5uiVEj3", + "ECaf0Mam2YxSwFLIllkKWdU28Y7BTKVyFyEsVvojWkdMaGNSghMmr3j54xVoLYqxjXOng0o6xiUigqHD", + "902oMJo7dTiAMO0bDuMzWzV63Mxd4FSEitw1jeWy4LqImwvJctDu3mfXfGdublFqjAOHbEo8kma6WQMi", + "6xKSNgFS7rxR+Jb2ngZAfoeGnwkGG/QLThhrSLVj1Yh9ZgjDn8Jgs+HbrFQrjCIcORA+Ny1a+OgJqCSq", + "wUk+m7buMI8Rv8L+aTAtv2dEVuGsU6bYf+5/xK3EZ+RPUti9J590lP2wTvK7pYMZkCpXrfM/EcvwPKYi", + "cX3ylTgaNwibIVQl0B5Emwgj9qGuXnxkF9ENwodxx0rw6eXOup4WqXhf0gxkqDEwe9z7wbSu7Dz37llD", + "VdpA1UBImfto6SM1baSfD/fSCHhUm96f9e60jcuMG+eYGnH746OzSlVZPsXnkyp3FN5M4CHtwjhCH5ER", + "YGTdjXuMaWrZdPIedYraHFsmb7SoziFrV5Xve/SPqYlGOHrXBKGWyMuocjtqtzCSp1GmzPsxZl01WMMk", + "GGca8lqjmvia7w6XHRvJGH3xt7PPHz/5+5PPv2CuASvECkybdbxXtqv1CxSyr/f5tJ6Ag+XZ9CaE7AOE", + "uGB/DEFVzab4s0bc1rQpRQdFy47RLycugMRxTJSLutFe4Tita/8fa7tSi7zzHUuh4LffM63KMl31oZGr", + "EgaU1G5FJhT3AqlAG2GsY4RdC6iwrUe0WaN6EHP/XlE2GSVzCPpjTwXCjrhcpRYy5lCL/Axju73ViMG2", + "Kj2vIkvPvnX5dxpp6FBoRK+YBbBKVV60F0uWgggjiHQUWesVn6gRj3xkG2ZL3rIpQvSe52nSiwtm7+f2", + "3WKuNs3p3SYmxItwKG9AmmP2ifG8BTfhJK1q/w/DPxKJGO6MazTL/S14RfJ9cLOi/JNAGwblJ8gDARiJ", + "tu3ESUaBYlEiYk1WArQnBANyX/z4vjUsHwwLQUhChwPgxeGzbbsmksGD8ztn9P2+QUq0lPdjlNBZ/qGI", + "3MB6m4sk2iKvNLEWDLElNRQLo3Br87yJYh55lQyCnbVSlrmXaVkmgqRJj4NnKiYc9yTQV7z89FzjG6GN", + "PUN8QPF6PDQqjpSNkUyoNDfL0/eST5o7ioq9u6nlKwzM/k9we5S85/xQ3gg/uM1QuYMV61fhVqBYb3aN", + "Y5KT1eMv2MIX26g05ML0jfvXQThpAkNBi6V3aIWtPRCJemidPyt7CzJeBk8c9kNk3mps9h7C9oj+zkxl", + "5OQmqTxFfQOySOAvxaPi4rwHrotbFma4WdqXKIHbkWlfhmWHpy6PUpu4S6c2MFzn5Nu6g9vERd2ubWrO", + "osn1Hd69e2sXU1INpWsxuO6Y6+hOijIcVZLhN8hyRDjyY/h5UxTz81jeW8rtOpKbu7cftSgPOqx0Mq1/", + "nM9WIMEIg7nE/+5rx3zauzRAQJkXhkeVYL1NuhhCTGKtncmjqaIc6hPSp/tuiZzXGNWY11rYHdYNDgo0", + "8fdkPqZvm9wePjdMY0vzd59Vl9DUbm8zgdQm3K7fKl7ifUQmPuluIVWesK8pw7c/KH+9t/h3+OwvT4tH", + "nz3+98VfHn3+KIenn3/56BH/8il//OVnj+HJXz5/+ggeL7/4cvGkePL0yeLpk6dffP5l/tnTx4unX3z5", + "7/ccH3IgE6Ahtf+z2f+bnZUrlZ29Os/eOGBbnPBKfAdub/CtvFRY19IhNceTCBsuytmz8NP/HU7YSa42", + "7fDh15mvzzRbW1uZZ6en19fXJ3GX0xWG/mdW1fn6NMyD1QY78sqr88ZHn/xwcEdb7TFuqieFM/z2+uuL", + "N+zs1flJSzCzZ7NHJ49OHvvS1pJXYvZs9hn+hKdnjft+ivk1T41PnX/axmol7Xav0WU9COd6BQW730Td", + "/FtjuTUPQvDOUpR4ZfzDEDE2qzgvkLh8jdIZVl1DZywE68mjR2EvvKQTXTinGP3x7MOsrW3fFyYGSH3T", + "ApyErK35OFz0T/JSqmvJMBkgHaB6s+F6RyvoYCMaHLeJrwwq2bW44hZm713vPs6ryhcsGEM5VrnqnvLQ", + "GQmkyXjvThglwvdlB0wK5cNiCbfE/t7kkIPJEruDjV45mEP6nCahojcIeZyhzZgQ1pwRUjsMED2fVXUC", + "nV9jYI3Zh7N5lISfoFFl0WB8gNFX9f8QjDrS9XfT7NkH99caeImJtdwfG0eoefikgRc7/39zzVcr0Cd+", + "ne6nqyen4RVy+sFnTPm479tp7BF2+qGTWKY40DN4PB1qcvohlMzeP2CnXLL3NY06TAR0X7PTBZbJmtoU", + "4tWNLwVp3px+wAf46O+nXoua/oiKELphT0OCppGWlIoj/bGDwg926xayfzjXJhov5zZf19XpB/wPku1H", + "Ou0lpDI5UYkOztrmcyYs4wulsQKzzdeOG4TSr8JELQdH/sz1ek4QhEr66F40e/Z2GP+FA7EwEooo7v5t", + "JYjOTK2QiOaUiCk0InCnfSsIv32Uffn+w+P540cf/8UJuv7Pzz/7ONF7/nkzLrtopNiJDd/fkuMNdDbt", + "ImmTGgY2fGR4WhiP7/Fb1RuINcg4UN+xN/zwrYQM+Okd8vhu3uEEf/+KFyykScC5H3+6uc8l+Yg7QZUE", + "6o/z2eefcvXn0pE8L4NIdkPh7YwOf8wUmN/slPA2n0klo2SKckVihkqlqhjhN8byG/CbC9frf/lNp+HA", + "yodxeKRt9cXdI78eukyaWnYQMsyG2AJeXHGZh2CsNjoC94skb08YjQNubWBZlyENSVWKJdXMV6oME5m6", + "qhzHWXLTUJYPyXAPZsqi0AzNapkrSa5TGP0SDMCYDQGNyOZSVJ0uYumoyldzp0isk7Dp/6xB79pd3wj3", + "8h28mVrnvt+ShRMe74CFdwe6Yxb+5Eg2+udf8f/sS+vpo798OghC8qI3YgOqtn/WS/OCbrBbXZpehqf6", + "G6d2K0/Rvfv0Q+e54j8Pnivd39vucYurjSogPCHUcmlQtbLv8+kH+jeaCLYVaLEBSWX5/a90c5xiUfjd", + "8OedzJM/DtfRycs88vNp0KimXsndlh86f3ZffmZd20JdUx3TpLyC1ycv2YZLvqIg/kYJ6e5BP0CbMpr9", + "WDUXlY/dZRzL76natlpiCmXxAf2NHR9vtMabayUkToAGWZyFL11XHl3gvgLmUId44SH7QRUwlI1SF6GH", + "sXMZNkchVWvy/d1oJyPG+/G4g4KGY/J6GJKR+1ib/t+n11xYJ0H53M2I0WFnC7w89YXaer+2tVEGX7Dg", + "S/RjnJUg+esp756LrgbFbdlYx4F6JfXVaxBGGoVgmvC5Nd7ExhAkl8YM8va923UD+ipQUqvbf3Z6itGV", + "a2XsKUqiXb1//PF9s9GhBHWz4e7bNlNarITkZeaVZG21ydmTk0ezj/8nAAD//1Xaw+sdDwEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go index 31d870e45e..b2f8e4a070 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -746,308 +746,308 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "z0/tvgEA", + "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", + "LwAA///BlGwvCb8BAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/private/routes.go b/daemon/algod/api/server/v2/generated/participating/private/routes.go index 981fddde61..3a53efec1e 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -21,6 +21,9 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + // Gets the merged config file. + // (GET /debug/settings/config) + GetConfig(ctx echo.Context) error // (GET /debug/settings/pprof) GetDebugSettingsProf(ctx echo.Context) error @@ -52,6 +55,17 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// GetConfig converts echo context to params. +func (w *ServerInterfaceWrapper) GetConfig(ctx echo.Context) error { + var err error + + ctx.Set(Api_keyScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetConfig(ctx) + return err +} + // GetDebugSettingsProf converts echo context to params. func (w *ServerInterfaceWrapper) GetDebugSettingsProf(ctx echo.Context) error { var err error @@ -219,6 +233,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.GET(baseURL+"/debug/settings/config", wrapper.GetConfig, m...) router.GET(baseURL+"/debug/settings/pprof", wrapper.GetDebugSettingsProf, m...) router.PUT(baseURL+"/debug/settings/pprof", wrapper.PutDebugSettingsProf, m...) router.GET(baseURL+"/v2/participation", wrapper.GetParticipationKeys, m...) @@ -233,235 +248,236 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAD//wm+5emgGwEA", + "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", + "AAD//1nr4yEUHQEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/generated/participating/public/routes.go b/daemon/algod/api/server/v2/generated/participating/public/routes.go index 0ada10df52..37794f0d92 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -383,32 +383,32 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 4a97d94d07..4e233a8b20 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -2104,6 +2104,11 @@ func (v2 *Handlers) GetDebugSettingsProf(ctx echo.Context) error { return ctx.JSON(http.StatusOK, response) } +// GetConfig returns the merged (defaults + overrides) config file in json. +func (v2 *Handlers) GetConfig(ctx echo.Context) error { + return ctx.JSON(http.StatusOK, v2.Node.Config()) +} + // PutDebugSettingsProf sets the mutex and blocking rates and returns the old values. func (v2 *Handlers) PutDebugSettingsProf(ctx echo.Context) error { req := ctx.Request() diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 1952c88e7a..c0e5c77990 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -2502,3 +2502,19 @@ func TestDebugExtraPprofEndpoint(t *testing.T) { require.Contains(t, string(body), `"block-rate":0`) } + +func TestGetConfigEndpoint(t *testing.T) { + partitiontest.PartitionTest(t) + + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, cannedStatusReportGolden) + defer releasefunc() + + err := handler.GetConfig(c) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + var responseConfig config.Local + + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &responseConfig)) + + require.Equal(t, handler.Node.Config(), responseConfig) +} From 046d01df5a6e25b67bb544eba1c990c0f8805d5b Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:18:59 -0400 Subject: [PATCH 167/218] tests: increase ListPeersForTopic wait time in TestP2PRelay (#6056) --- network/p2pNetwork_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 7c94be98e4..3f77d55f69 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -880,7 +880,7 @@ func TestP2PRelay(t *testing.T) { // add a netC with listening address set and enable relaying on netB // ensure all messages from netB and netC are received by netA cfg.NetAddress = "127.0.0.1:0" - log.Debugf("Starting netB with phonebook addresses %v", phoneBookAddresses) + log.Debugf("Starting netC with phonebook addresses %v", phoneBookAddresses) netC, err := NewP2PNetwork(log.With("net", "netC"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) err = netC.Start() @@ -892,11 +892,11 @@ func TestP2PRelay(t *testing.T) { require.Eventually( t, func() bool { - return len(netA.service.ListPeersForTopic(p2p.TXTopicName)) > 0 && + return len(netA.service.ListPeersForTopic(p2p.TXTopicName)) >= 2 && len(netB.service.ListPeersForTopic(p2p.TXTopicName)) > 0 && len(netC.service.ListPeersForTopic(p2p.TXTopicName)) > 0 }, - 2*time.Second, + 10*time.Second, // wait until netC node gets actually connected to netA after starting 50*time.Millisecond, ) From a9641a3edccd102099ebeb1a4bb6c718cf25f212 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:07:45 -0400 Subject: [PATCH 168/218] ci: pin reviewdog version to v0.18.1 (#6058) --- .github/workflows/reviewdog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index 1eff6ac578..b02d99e7cc 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -69,7 +69,7 @@ jobs: cd ../../ - name: Install reviewdog run: | - curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/v0.17.4/install.sh | sh -s + curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/v0.18.1/install.sh | sh -s -- v0.18.1 reviewdog --version - name: Build custom linters run: | From 292932940db138c000b2a31e994ce44e91f0fcf3 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:00:37 -0400 Subject: [PATCH 169/218] algod: fix telemetry lookup if phonebook is used (#6061) --- cmd/algod/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/algod/main.go b/cmd/algod/main.go index c4d1c09bc8..311b1507e6 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -362,7 +362,7 @@ func run() int { // If the telemetry URI is not set, periodically check SRV records for new telemetry URI if remoteTelemetryEnabled && log.GetTelemetryURI() == "" { - toolsnet.StartTelemetryURIUpdateService(time.Minute, cfg, s.Genesis.Network, log, done) + toolsnet.StartTelemetryURIUpdateService(time.Minute, cfgCopy, s.Genesis.Network, log, done) } currentVersion := config.GetCurrentVersion() From cf99017fa74daf738710165cefcb9bc95470da15 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:01:07 -0400 Subject: [PATCH 170/218] tests: fix concurrent accout commits in TestLedgerVerifiesOldStateProofs (#6060) --- ledger/ledger_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 968e6d8b21..f816ebb837 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -1683,6 +1683,15 @@ func TestLedgerVerifiesOldStateProofs(t *testing.T) { backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) defer backlogPool.Shutdown() + // wait all pending commits to finish + l.trackers.accountsWriting.Wait() + + // quit the commitSyncer goroutine: this test flushes manually with triggerTrackerFlush + l.trackers.ctxCancel() + l.trackers.ctxCancel = nil + <-l.trackers.commitSyncerClosed + l.trackers.commitSyncerClosed = nil + triggerTrackerFlush(t, l) l.WaitForCommit(l.Latest()) blk := createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) @@ -1714,7 +1723,7 @@ func TestLedgerVerifiesOldStateProofs(t *testing.T) { } l.acctsOnline.voters.votersMu.Unlock() - // However, we are still able to very a state proof sicne we use the tracker + // However, we are still able to very a state proof since we use the tracker blk = createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) _, err = l.Validate(context.Background(), blk, backlogPool) require.ErrorContains(t, err, "state proof crypto error") @@ -2934,7 +2943,7 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi // wait all pending commits to finish l.trackers.accountsWriting.Wait() - // quit the commitSyncer goroutine + // quit the commitSyncer goroutine: this test flushes manually with triggerTrackerFlush l.trackers.ctxCancel() l.trackers.ctxCancel = nil <-l.trackers.commitSyncerClosed From b46e2c1c1009ed1ae36d1bd9c40a8b45b732e7df Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:01:41 -0400 Subject: [PATCH 171/218] tests: disable TestP2PRelay on CI (#6059) --- ledger/ledger_test.go | 5 ++++- network/p2pNetwork_test.go | 32 ++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index f816ebb837..c97040f42c 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -1801,6 +1801,9 @@ func TestLedgerMemoryLeak(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Info) // prevent spamming with ledger.AddValidatedBlock debug message deadlock.Opts.Disable = true // catchpoint writing might take long + defer func() { + deadlock.Opts.Disable = false + }() l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) require.NoError(t, err) defer l.Close() @@ -2907,7 +2910,7 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi const inMem = true log := logging.TestingLog(t) - log.SetLevel(logging.Info) + log.SetLevel(logging.Debug) l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) require.NoError(t, err) defer l.Close() diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 3f77d55f69..5b3470689f 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -22,6 +22,9 @@ import ( "fmt" "io" "net/http" + "os" + "slices" + "strings" "sync" "sync/atomic" "testing" @@ -52,6 +55,13 @@ func (n *P2PNetwork) hasPeers() bool { return len(n.wsPeers) > 0 } +func (n *P2PNetwork) hasPeer(peerID peer.ID) bool { + n.wsPeersLock.RLock() + defer n.wsPeersLock.RUnlock() + _, ok := n.wsPeers[peerID] + return ok +} + func TestP2PSubmitTX(t *testing.T) { partitiontest.PartitionTest(t) @@ -794,8 +804,14 @@ func TestP2PHTTPHandler(t *testing.T) { func TestP2PRelay(t *testing.T) { partitiontest.PartitionTest(t) + if strings.ToUpper(os.Getenv("CIRCLECI")) == "TRUE" { + t.Skip("Flaky on CIRCLECI") + } + cfg := config.GetDefaultLocal() + cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses cfg.ForceFetchTransactions = true + cfg.BaseLoggerDebugLevel = 5 log := logging.TestingLog(t) log.Debugln("Starting netA") netA, err := NewP2PNetwork(log.With("net", "netA"), cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) @@ -883,6 +899,7 @@ func TestP2PRelay(t *testing.T) { log.Debugf("Starting netC with phonebook addresses %v", phoneBookAddresses) netC, err := NewP2PNetwork(log.With("net", "netC"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) + require.True(t, netC.relayMessages) err = netC.Start() require.NoError(t, err) defer netC.Stop() @@ -892,16 +909,23 @@ func TestP2PRelay(t *testing.T) { require.Eventually( t, func() bool { - return len(netA.service.ListPeersForTopic(p2p.TXTopicName)) >= 2 && - len(netB.service.ListPeersForTopic(p2p.TXTopicName)) > 0 && - len(netC.service.ListPeersForTopic(p2p.TXTopicName)) > 0 + netAtopicPeers := netA.service.ListPeersForTopic(p2p.TXTopicName) + netBtopicPeers := netB.service.ListPeersForTopic(p2p.TXTopicName) + netCtopicPeers := netC.service.ListPeersForTopic(p2p.TXTopicName) + netBConnected := slices.Contains(netAtopicPeers, netB.service.ID()) + netCConnected := slices.Contains(netAtopicPeers, netC.service.ID()) + return len(netAtopicPeers) >= 2 && + len(netBtopicPeers) > 0 && + len(netCtopicPeers) > 0 && + netBConnected && netCConnected }, 10*time.Second, // wait until netC node gets actually connected to netA after starting 50*time.Millisecond, ) require.Eventually(t, func() bool { - return netA.hasPeers() && netB.hasPeers() && netC.hasPeers() + return netA.hasPeers() && netB.hasPeers() && netC.hasPeers() && + netA.hasPeer(netB.service.ID()) && netA.hasPeer(netC.service.ID()) }, 2*time.Second, 50*time.Millisecond) const expectedMsgs = 10 From 5869a00445c7860db42e12da10e8169e62b53c1e Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:02:45 -0400 Subject: [PATCH 172/218] logging: change transaction pool re-evaluation message from warn to info (#6047) --- data/pools/transactionPool.go | 11 ++++++++--- logging/telemetryspec/metric.go | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index afe12f2363..a2eef08bc3 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -29,6 +29,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/transactions/logic" "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" @@ -784,15 +785,19 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIDs map[transact case *ledgercore.LeaseInLedgerError: asmStats.LeaseErrorCount++ stats.RemovedInvalidCount++ - pool.log.Infof("Cannot re-add pending transaction to pool: %v", err) + pool.log.Infof("Pending transaction in pool no longer valid: %v", err) case *transactions.MinFeeError: asmStats.MinFeeErrorCount++ stats.RemovedInvalidCount++ - pool.log.Infof("Cannot re-add pending transaction to pool: %v", err) + pool.log.Infof("Pending transaction in pool no longer valid: %v", err) + case logic.EvalError: + asmStats.LogicErrorCount++ + stats.RemovedInvalidCount++ + pool.log.Infof("Pending transaction in pool no longer valid: %v", err) default: asmStats.InvalidCount++ stats.RemovedInvalidCount++ - pool.log.Warnf("Cannot re-add pending transaction to pool: %v", err) + pool.log.Infof("Pending transaction in pool no longer valid: %v", err) } } } diff --git a/logging/telemetryspec/metric.go b/logging/telemetryspec/metric.go index 8ab269a2c2..2d43baae0d 100644 --- a/logging/telemetryspec/metric.go +++ b/logging/telemetryspec/metric.go @@ -46,6 +46,7 @@ type AssembleBlockStats struct { IncludedCount int // number of transactions that are included in a block InvalidCount int // number of transaction groups that are included in a block MinFeeErrorCount int // number of transactions excluded because the fee is too low + LogicErrorCount int // number of transactions excluded due to logic error (contract no longer valid) ExpiredCount int // number of transactions removed because of expiration ExpiredLongLivedCount int // number of expired transactions with non-super short LastValid values LeaseErrorCount int // number of transactions removed because it has an already used lease @@ -115,6 +116,7 @@ func (m AssembleBlockStats) String() string { b.WriteString(fmt.Sprintf("IncludedCount:%d, ", m.IncludedCount)) b.WriteString(fmt.Sprintf("InvalidCount:%d, ", m.InvalidCount)) b.WriteString(fmt.Sprintf("MinFeeErrorCount:%d, ", m.MinFeeErrorCount)) + b.WriteString(fmt.Sprintf("LogicErrorCount:%d, ", m.LogicErrorCount)) b.WriteString(fmt.Sprintf("ExpiredCount:%d, ", m.ExpiredCount)) b.WriteString(fmt.Sprintf("ExpiredLongLivedCount:%d, ", m.ExpiredLongLivedCount)) b.WriteString(fmt.Sprintf("LeaseErrorCount:%d, ", m.LeaseErrorCount)) From 1493410add4aaa4a5537037de1d88f166ccc85db Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:49:26 -0400 Subject: [PATCH 173/218] P2P: Disable circuit relaying via libp2p.NoListenAddrs (#6064) --- network/p2p/p2p.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index ac0489d5e1..2877c6b2f3 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -115,12 +115,6 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. listenAddr = "/ip4/0.0.0.0/tcp/0" } - // the libp2p.NoListenAddrs builtin disables relays but this one does not - var noListenAddrs = func(cfg *libp2p.Config) error { - cfg.ListenAddrs = []multiaddr.Multiaddr{} - return nil - } - var disableMetrics = func(cfg *libp2p.Config) error { return nil } metrics.DefaultRegistry().Register(&metrics.PrometheusDefaultMetrics) @@ -130,7 +124,7 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. libp2p.Transport(tcp.NewTCPTransport), libp2p.Muxer("/yamux/1.0.0", &ymx), libp2p.Peerstore(pstore), - noListenAddrs, + libp2p.NoListenAddrs, libp2p.Security(noise.ID, noise.New), disableMetrics, ) From 8a44d87aac9e276bc84ef0d61697f777644f83bc Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:43:14 -0400 Subject: [PATCH 174/218] rest api: Fix to Disable API authentication (#6067) Co-authored-by: Gary Malouf <982483+gmalouf@users.noreply.github.com> --- daemon/algod/api/server/router.go | 19 ++++++++++++++----- test/e2e-go/restAPI/other/misc_test.go | 19 ++++++++++++++----- test/framework/fixtures/libgoalFixture.go | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/daemon/algod/api/server/router.go b/daemon/algod/api/server/router.go index cd0899c176..0b02bb8566 100644 --- a/daemon/algod/api/server/router.go +++ b/daemon/algod/api/server/router.go @@ -19,10 +19,11 @@ package server import ( "fmt" - "golang.org/x/sync/semaphore" "net" "net/http" + "golang.org/x/sync/semaphore" + "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -74,18 +75,26 @@ func registerHandlers(router *echo.Echo, prefix string, routes lib.Routes, ctx l // NewRouter builds and returns a new router with our REST handlers registered. func NewRouter(logger logging.Logger, node APINodeInterface, shutdown <-chan struct{}, apiToken string, adminAPIToken string, listener net.Listener, numConnectionsLimit uint64) *echo.Echo { - if err := tokens.ValidateAPIToken(apiToken); err != nil { - logger.Errorf("Invalid apiToken was passed to NewRouter ('%s'): %v", apiToken, err) - } + // check admin token and init admin middleware if err := tokens.ValidateAPIToken(adminAPIToken); err != nil { logger.Errorf("Invalid adminAPIToken was passed to NewRouter ('%s'): %v", adminAPIToken, err) } adminMiddleware := []echo.MiddlewareFunc{ middlewares.MakeAuth(TokenHeader, []string{adminAPIToken}), } + + // check public api tokens and init public middleware publicMiddleware := []echo.MiddlewareFunc{ middleware.BodyLimit(MaxRequestBodyBytes), - middlewares.MakeAuth(TokenHeader, []string{adminAPIToken, apiToken}), + } + if apiToken == "" { + logger.Warn("Running with public API authentication disabled") + } else { + if err := tokens.ValidateAPIToken(apiToken); err != nil { + logger.Errorf("Invalid apiToken was passed to NewRouter ('%s'): %v", apiToken, err) + } + publicMiddleware = append(publicMiddleware, middlewares.MakeAuth(TokenHeader, []string{adminAPIToken, apiToken})) + } e := echo.New() diff --git a/test/e2e-go/restAPI/other/misc_test.go b/test/e2e-go/restAPI/other/misc_test.go index 3f9da07c4a..eeaff9fcd1 100644 --- a/test/e2e-go/restAPI/other/misc_test.go +++ b/test/e2e-go/restAPI/other/misc_test.go @@ -39,12 +39,12 @@ func TestDisabledAPIConfig(t *testing.T) { localFixture.Setup(t, filepath.Join("nettemplates", "DisableAPIAuth.json")) defer localFixture.Shutdown() - testClient := localFixture.LibGoalClient + libgoalClient := localFixture.LibGoalClient - statusResponse, err := testClient.Status() + statusResponse, err := libgoalClient.Status() a.NoError(err) a.NotEmpty(statusResponse) - statusResponse2, err := testClient.Status() + statusResponse2, err := libgoalClient.Status() a.NoError(err) a.NotEmpty(statusResponse2) a.True(statusResponse2.LastRound >= statusResponse.LastRound) @@ -58,12 +58,21 @@ func TestDisabledAPIConfig(t *testing.T) { assert.True(t, os.IsNotExist(err)) // check public api works without a token - testClient.WaitForRound(1) + url, err := localFixture.NC.ServerURL() + a.NoError(err) + testClient := client.MakeRestClient(url, "") // empty token + + _, err = testClient.WaitForBlock(1) + assert.NoError(t, err) _, err = testClient.Block(1) assert.NoError(t, err) + _, err = testClient.Status() + a.NoError(err) + // check admin api works with the generated token - _, err = testClient.GetParticipationKeys() + _, err = libgoalClient.GetParticipationKeys() assert.NoError(t, err) + // check admin api doesn't work with an invalid token algodURL, err := nc.ServerURL() assert.NoError(t, err) diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index de1a06623d..bd4f615ae7 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -390,7 +390,7 @@ func (f *LibGoalFixture) dumpLogs(filePath string) { fmt.Fprintf(os.Stderr, "%s/%s:\n", parts[len(parts)-2], parts[len(parts)-1]) // Primary/node.log scanner := bufio.NewScanner(file) for scanner.Scan() { - fmt.Fprint(os.Stderr, scanner.Text()) + fmt.Fprintln(os.Stderr, scanner.Text()) } fmt.Fprintln(os.Stderr) } From 48a539f3307ea970c7a1fd67266c15108a5fdebc Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:13:26 -0400 Subject: [PATCH 175/218] rest api: make RawTransactionAsync developer api (#6069) --- daemon/algod/api/algod.oas2.json | 4 + daemon/algod/api/algod.oas3.yml | 5 + .../v2/generated/experimental/routes.go | 451 +++++++++--------- daemon/algod/api/server/v2/handlers.go | 3 + .../algod/api/server/v2/test/handlers_test.go | 37 +- 5 files changed, 266 insertions(+), 234 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 4cc1e0ced7..9b11f26642 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1565,6 +1565,7 @@ "/v2/transactions/async": { "post": { "tags": [ + "public", "experimental" ], "consumes": [ @@ -1603,6 +1604,9 @@ "$ref": "#/definitions/ErrorResponse" } }, + "404": { + "description": "Developer or Experimental API not enabled" + }, "500": { "description": "Internal Error", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 2f57b62453..9b7ec0a37d 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -7012,6 +7012,10 @@ }, "description": "Invalid API Token" }, + "404": { + "content": {}, + "description": "Developer or Experimental API not enabled" + }, "500": { "content": { "application/json": { @@ -7039,6 +7043,7 @@ }, "summary": "Fast track for broadcasting a raw transaction or transaction group to the network through the tx handler without performing most of the checks and reporting detailed errors. Should be only used for development and performance testing.", "tags": [ + "public", "experimental" ], "x-codegen-request-body-name": "rawtxn" diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index 83312b906f..3d5c749701 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -130,231 +130,232 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AAD//6UnopQICgEA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 4e233a8b20..b638825ec4 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1102,6 +1102,9 @@ func (v2 *Handlers) RawTransactionAsync(ctx echo.Context) error { if !v2.Node.Config().EnableExperimentalAPI { return ctx.String(http.StatusNotFound, "/transactions/async was not enabled in the configuration file by setting the EnableExperimentalAPI to true") } + if !v2.Node.Config().EnableDeveloperAPI { + return ctx.String(http.StatusNotFound, "/transactions/async was not enabled in the configuration file by setting the EnableDeveloperAPI to true") + } txgroup, err := decodeTxGroup(ctx.Request().Body, config.MaxTxGroupSize) if err != nil { return badRequest(ctx, err, err.Error(), v2.Log) diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index c0e5c77990..e830c4892c 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -853,12 +853,29 @@ func prepareTransactionTest(t *testing.T, txnToUse int, txnPrep func(transaction return } -func postTransactionTest(t *testing.T, txnToUse int, expectedCode int, method string, enableExperimental bool) { +type postTransactionOpt func(cfg *config.Local) + +func enableExperimentalAPI() postTransactionOpt { + return func(cfg *config.Local) { + cfg.EnableExperimentalAPI = true + } +} + +func enableDeveloperAPI() postTransactionOpt { + return func(cfg *config.Local) { + cfg.EnableDeveloperAPI = true + } +} + +func postTransactionTest(t *testing.T, txnToUse int, expectedCode int, method string, opts ...postTransactionOpt) { + cfg := config.GetDefaultLocal() + for _, opt := range opts { + opt(&cfg) + } + txnPrep := func(stxn transactions.SignedTxn) []byte { return protocol.Encode(&stxn) } - cfg := config.GetDefaultLocal() - cfg.EnableExperimentalAPI = enableExperimental handler, c, rec, releasefunc := prepareTransactionTest(t, txnToUse, txnPrep, cfg) defer releasefunc() results := reflect.ValueOf(&handler).MethodByName(method).Call([]reflect.Value{reflect.ValueOf(c)}) @@ -873,18 +890,20 @@ func TestPostTransaction(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - postTransactionTest(t, -1, 400, "RawTransaction", false) - postTransactionTest(t, 0, 200, "RawTransaction", false) + postTransactionTest(t, -1, 400, "RawTransaction") + postTransactionTest(t, 0, 200, "RawTransaction") } func TestPostTransactionAsync(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - postTransactionTest(t, -1, 404, "RawTransactionAsync", false) - postTransactionTest(t, 0, 404, "RawTransactionAsync", false) - postTransactionTest(t, -1, 400, "RawTransactionAsync", true) - postTransactionTest(t, 0, 200, "RawTransactionAsync", true) + postTransactionTest(t, -1, 404, "RawTransactionAsync") + postTransactionTest(t, 0, 404, "RawTransactionAsync") + postTransactionTest(t, -1, 404, "RawTransactionAsync", enableDeveloperAPI()) + postTransactionTest(t, -1, 404, "RawTransactionAsync", enableExperimentalAPI()) + postTransactionTest(t, -1, 400, "RawTransactionAsync", enableExperimentalAPI(), enableDeveloperAPI()) + postTransactionTest(t, 0, 200, "RawTransactionAsync", enableExperimentalAPI(), enableDeveloperAPI()) } func simulateTransactionTest(t *testing.T, txnToUse int, format string, expectedCode int) { From 1fa0ef7e0c0169d857dedb3747362a08f2355b63 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:00:01 -0400 Subject: [PATCH 176/218] P2P: Introduce profiles for hybridRelay, hybridArchival, and hybridClient. (#6062) Co-authored-by: chris erway Co-authored-by: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> --- cmd/algocfg/profileCommand.go | 70 ++++++++++++++++++++++++++---- cmd/algocfg/profileCommand_test.go | 62 ++++++++++++++++++++++++++ config/config.go | 8 ++++ network/p2p/p2p.go | 8 +++- network/p2pNetwork.go | 8 +++- network/p2pNetwork_test.go | 16 ++++++- 6 files changed, 159 insertions(+), 13 deletions(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 4cd9bf5c9e..076cb65a64 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -66,8 +66,8 @@ var ( }, } - relay = configUpdater{ - description: "Relay consensus messages across the network and support catchup.", + wsRelay = configUpdater{ + description: "Relay consensus messages across the ws network and support recent catchup.", updateFunc: func(cfg config.Local) config.Local { cfg.MaxBlockHistoryLookback = 22000 // Enough to support 2 catchpoints with some wiggle room for nodes to catch up from the older one cfg.CatchpointFileHistoryLength = 3 @@ -80,7 +80,7 @@ var ( } archival = configUpdater{ - description: "Store the full chain history and support catchup.", + description: "Store the full chain history and support full catchup.", updateFunc: func(cfg config.Local) config.Local { cfg.Archival = true cfg.EnableLedgerService = true @@ -91,13 +91,67 @@ var ( }, } + hybridRelay = configUpdater{ + description: "Relay consensus messages across both ws and p2p networks, also support recent catchup.", + updateFunc: func(cfg config.Local) config.Local { + // WS relay config defaults + cfg.MaxBlockHistoryLookback = 22000 // Enough to support 2 catchpoints with some wiggle room for nodes to catch up from the older one + cfg.CatchpointFileHistoryLength = 3 + cfg.CatchpointTracking = 2 + cfg.EnableLedgerService = true + cfg.EnableBlockService = true + cfg.NetAddress = ":4160" + // This should be set to the public address of the node if public access is desired + cfg.PublicAddress = config.PlaceholderPublicAddress + + // P2P config defaults + cfg.EnableP2PHybridMode = true + cfg.P2PNetAddress = ":4190" + cfg.EnableDHTProviders = true + return cfg + }, + } + + hybridArchival = configUpdater{ + description: "Store the full chain history, support full catchup, P2P enabled, discoverable via DHT.", + updateFunc: func(cfg config.Local) config.Local { + cfg.Archival = true + cfg.EnableLedgerService = true + cfg.EnableBlockService = true + cfg.NetAddress = ":4160" + cfg.EnableGossipService = false + // This should be set to the public address of the node + cfg.PublicAddress = config.PlaceholderPublicAddress + + // P2P config defaults + cfg.EnableP2PHybridMode = true + cfg.P2PNetAddress = ":4190" + cfg.EnableDHTProviders = true + return cfg + }, + } + + hybridClient = configUpdater{ + description: "Participate in consensus or simply ensure chain health by validating blocks and supporting P2P traffic propagation.", + updateFunc: func(cfg config.Local) config.Local { + + // P2P config defaults + cfg.EnableP2PHybridMode = true + cfg.EnableDHTProviders = true + return cfg + }, + } + // profileNames are the supported pre-configurations of config values profileNames = map[string]configUpdater{ - "participation": participation, - "conduit": conduit, - "relay": relay, - "archival": archival, - "development": development, + "participation": participation, + "conduit": conduit, + "wsRelay": wsRelay, + "archival": archival, + "development": development, + "hybridRelay": hybridRelay, + "hybridArchival": hybridArchival, + "hybridClient": hybridClient, } forceUpdate bool diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go index d8bf715534..8d7d95a26e 100644 --- a/cmd/algocfg/profileCommand_test.go +++ b/cmd/algocfg/profileCommand_test.go @@ -17,6 +17,7 @@ package main import ( + "github.com/algorand/go-algorand/config" "testing" "github.com/stretchr/testify/require" @@ -62,4 +63,65 @@ func Test_getConfigForArg(t *testing.T) { require.Equal(t, ":4160", cfg.NetAddress) require.False(t, cfg.EnableGossipService) }) + + t.Run("valid config test hybrid relay", func(t *testing.T) { + t.Parallel() + cfg, err := getConfigForArg("hybridRelay") + require.NoError(t, err) + + require.False(t, cfg.Archival) + require.Equal(t, uint64(22000), cfg.MaxBlockHistoryLookback) + require.Equal(t, 3, cfg.CatchpointFileHistoryLength) + require.Equal(t, int64(2), cfg.CatchpointTracking) + require.True(t, cfg.EnableLedgerService) + require.True(t, cfg.EnableBlockService) + require.Equal(t, ":4160", cfg.NetAddress) + require.True(t, cfg.EnableGossipService) + require.Equal(t, config.PlaceholderPublicAddress, cfg.PublicAddress) + + require.True(t, cfg.EnableP2PHybridMode) + require.Equal(t, ":4190", cfg.P2PNetAddress) + require.True(t, cfg.EnableDHTProviders) + }) + + t.Run("valid config test hybrid archival", func(t *testing.T) { + t.Parallel() + cfg, err := getConfigForArg("hybridArchival") + require.NoError(t, err) + + require.True(t, cfg.Archival) + require.Equal(t, uint64(0), cfg.MaxBlockHistoryLookback) + require.Equal(t, 365, cfg.CatchpointFileHistoryLength) + require.Equal(t, int64(0), cfg.CatchpointTracking) + require.True(t, cfg.EnableLedgerService) + require.True(t, cfg.EnableBlockService) + require.Equal(t, ":4160", cfg.NetAddress) + require.False(t, cfg.EnableGossipService) + require.Equal(t, config.PlaceholderPublicAddress, cfg.PublicAddress) + + require.True(t, cfg.EnableP2PHybridMode) + require.Equal(t, ":4190", cfg.P2PNetAddress) + require.True(t, cfg.EnableDHTProviders) + }) + + t.Run("valid config test hybrid client", func(t *testing.T) { + t.Parallel() + cfg, err := getConfigForArg("hybridClient") + require.NoError(t, err) + + require.False(t, cfg.Archival) + require.Equal(t, uint64(0), cfg.MaxBlockHistoryLookback) + require.Equal(t, 365, cfg.CatchpointFileHistoryLength) + require.Equal(t, int64(0), cfg.CatchpointTracking) + require.False(t, cfg.EnableLedgerService) + require.False(t, cfg.EnableBlockService) + require.Empty(t, cfg.NetAddress) + // True because it is the default value, net address is blank so has no effect in practice + require.True(t, cfg.EnableGossipService) + require.Equal(t, "", cfg.PublicAddress) + + require.True(t, cfg.EnableP2PHybridMode) + require.Equal(t, "", cfg.P2PNetAddress) + require.True(t, cfg.EnableDHTProviders) + }) } diff --git a/config/config.go b/config/config.go index 2d5d0bdbfe..87440cc58a 100644 --- a/config/config.go +++ b/config/config.go @@ -104,6 +104,9 @@ const CatchpointTrackingModeTracked = 1 // as long as CatchpointInterval > 0 const CatchpointTrackingModeStored = 2 +// PlaceholderPublicAddress is a placeholder for the public address generated in certain profiles +const PlaceholderPublicAddress = "PLEASE_SET_ME" + // LoadConfigFromDisk returns a Local config structure based on merging the defaults // with settings loaded from the config file from the custom dir. If the custom file // cannot be loaded, the default config is returned (with the error from loading the @@ -145,6 +148,11 @@ func mergeConfigFromFile(configpath string, source Local) (Local, error) { err = loadConfig(f, &source) + // If the PublicAddress in config file has the PlaceholderPublicAddress, treat it as if it were empty + if source.PublicAddress == PlaceholderPublicAddress { + source.PublicAddress = "" + } + if source.NetAddress != "" { source.EnableLedgerService = true source.EnableBlockService = true diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 2877c6b2f3..f67f79f427 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -112,7 +112,8 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. listenAddr = parsedListenAddr } } else { - listenAddr = "/ip4/0.0.0.0/tcp/0" + // don't listen if NetAddress is not set. + listenAddr = "" } var disableMetrics = func(cfg *libp2p.Config) error { return nil } @@ -163,6 +164,11 @@ func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h ho // Start starts the P2P service func (s *serviceImpl) Start() error { + if s.listenAddr == "" { + // don't listen if no listen address configured + return nil + } + listenAddr, err := multiaddr.NewMultiaddr(s.listenAddr) if err != nil { s.log.Errorf("failed to create multiaddress: %s", err) diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 1ad49bd045..4d6efdac83 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -346,8 +346,11 @@ func (n *P2PNetwork) Start() error { go n.handler.messageHandlerThread(&n.wg, n.wsPeersConnectivityCheckTicker.C, n, "network", "P2PNetwork") } - n.wg.Add(1) - go n.httpdThread() + // start the HTTP server if configured to listen + if n.config.NetAddress != "" { + n.wg.Add(1) + go n.httpdThread() + } n.wg.Add(1) go n.broadcaster.broadcastThread(&n.wg, n, "network", "P2PNetwork") @@ -471,6 +474,7 @@ func (n *P2PNetwork) meshThread() { func (n *P2PNetwork) httpdThread() { defer n.wg.Done() + err := n.httpServer.Serve() if err != nil { n.log.Errorf("Error serving libp2phttp: %v", err) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 5b3470689f..ff1f40a63c 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -67,6 +67,7 @@ func TestP2PSubmitTX(t *testing.T) { cfg := config.GetDefaultLocal() cfg.ForceFetchTransactions = true + cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) @@ -159,6 +160,7 @@ func TestP2PSubmitTXNoGossip(t *testing.T) { cfg := config.GetDefaultLocal() cfg.ForceFetchTransactions = true + cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) @@ -189,6 +191,8 @@ func TestP2PSubmitTXNoGossip(t *testing.T) { // run netC in NPN mode (no relay => no gossip sup => no TX receiving) cfg.ForceFetchTransactions = false + // Have to unset NetAddress to get IsGossipServer to return false + cfg.NetAddress = "" netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) netC.Start() @@ -253,6 +257,7 @@ func TestP2PSubmitWS(t *testing.T) { partitiontest.PartitionTest(t) cfg := config.GetDefaultLocal() + cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) @@ -584,6 +589,7 @@ func TestP2PNetworkDHTCapabilities(t *testing.T) { partitiontest.PartitionTest(t) cfg := config.GetDefaultLocal() + cfg.NetAddress = "127.0.0.1:0" cfg.EnableDHTProviders = true log := logging.TestingLog(t) @@ -744,6 +750,7 @@ func TestP2PHTTPHandler(t *testing.T) { cfg := config.GetDefaultLocal() cfg.EnableDHTProviders = true cfg.GossipFanout = 1 + cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) @@ -812,6 +819,7 @@ func TestP2PRelay(t *testing.T) { cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses cfg.ForceFetchTransactions = true cfg.BaseLoggerDebugLevel = 5 + cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) log.Debugln("Starting netA") netA, err := NewP2PNetwork(log.With("net", "netA"), cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) @@ -829,6 +837,8 @@ func TestP2PRelay(t *testing.T) { multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} + // Explicitly unset NetAddress for netB + cfg.NetAddress = "" log.Debugf("Starting netB with phonebook addresses %v", phoneBookAddresses) netB, err := NewP2PNetwork(log.With("net", "netB"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) @@ -880,8 +890,8 @@ func TestP2PRelay(t *testing.T) { counterHandler, counterDone := makeCounterHandler(1, &counter, nil) netA.RegisterProcessors(counterHandler) - // send 5 messages from both netB to netA - // since there is no node with listening address set => no messages should be received + // send 5 messages from netB to netA + // since relaying is disabled on net B => no messages should be received by net A for i := 0; i < 5; i++ { err := netB.Relay(context.Background(), protocol.TxnTag, []byte{1, 2, 3, byte(i)}, true, nil) require.NoError(t, err) @@ -1031,6 +1041,7 @@ func TestP2PWantTXGossip(t *testing.T) { net.wantTXGossip.Store(true) net.nodeInfo = &nopeNodeInfo{} net.config.ForceFetchTransactions = false + net.config.NetAddress = "" net.relayMessages = false net.OnNetworkAdvance() require.Eventually(t, func() bool { net.wg.Wait(); return true }, 1*time.Second, 50*time.Millisecond) @@ -1048,6 +1059,7 @@ func TestP2PWantTXGossip(t *testing.T) { net.wantTXGossip.Store(false) net.nodeInfo = &nopeNodeInfo{} net.config.ForceFetchTransactions = false + net.config.NetAddress = "" net.relayMessages = true net.OnNetworkAdvance() require.Eventually(t, func() bool { return mockService.count.Load() == 3 }, 1*time.Second, 50*time.Millisecond) From 9c9367018eaea37653e70f368af0aafff18527e7 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:06:23 -0400 Subject: [PATCH 177/218] p2p: hybrid node net identity for connection deduplication (#6035) --- config/config.go | 20 +- config/config_test.go | 185 +++++++++++++++--- config/localTemplate.go | 42 +++- config/local_defaults.go | 1 + daemon/algod/server.go | 24 ++- installer/config.json.example | 1 + netdeploy/remote/deployedNetwork.go | 10 + netdeploy/remote/nodeConfig.go | 2 + netdeploy/remote/nodecfg/nodeConfigurator.go | 22 ++- netdeploy/remote/nodecfg/nodeDir.go | 48 ++++- network/hybridNetwork.go | 10 +- network/hybridNetwork_test.go | 183 +++++++++++++++++ network/netidentity.go | 89 +++++++-- network/netidentity_test.go | 103 ++++++++-- network/p2p/logger.go | 23 ++- network/p2p/p2p.go | 29 ++- network/p2p/peerID_test.go | 16 ++ network/p2pNetwork.go | 90 +++++---- network/p2pNetwork_test.go | 54 ++--- network/requestLogger_test.go | 13 +- network/requestTracker_test.go | 13 +- network/wsNetwork.go | 34 ++-- network/wsNetwork_test.go | 80 ++++---- node/follower_node.go | 2 +- node/node.go | 4 +- node/node_test.go | 1 + test/testdata/configs/config-v34.json | 1 + .../recipes/scenario1s-p2p/Makefile | 10 +- .../recipes/scenario1s-p2p/README.md | 9 +- .../scenario1s-p2p/copy-node-configs.py | 136 +++++++++++-- 30 files changed, 994 insertions(+), 261 deletions(-) create mode 100644 network/hybridNetwork_test.go diff --git a/config/config.go b/config/config.go index 87440cc58a..65d711cacc 100644 --- a/config/config.go +++ b/config/config.go @@ -23,6 +23,7 @@ import ( "os" "os/user" "path/filepath" + "strings" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/codecs" @@ -147,7 +148,17 @@ func mergeConfigFromFile(configpath string, source Local) (Local, error) { defer f.Close() err = loadConfig(f, &source) + if err != nil { + return source, err + } + source, err = enrichNetworkingConfig(source) + return source, err +} +// enrichNetworkingConfig makes the following tweaks to the config: +// - If NetAddress is set, enable the ledger and block services +// - If EnableP2PHybridMode is set, require PublicAddress to be set +func enrichNetworkingConfig(source Local) (Local, error) { // If the PublicAddress in config file has the PlaceholderPublicAddress, treat it as if it were empty if source.PublicAddress == PlaceholderPublicAddress { source.PublicAddress = "" @@ -163,8 +174,13 @@ func mergeConfigFromFile(configpath string, source Local) (Local, error) { source.GossipFanout = defaultRelayGossipFanout } } - - return source, err + // In hybrid mode we want to prevent connections from the same node over both P2P and WS. + // The only way it is supported at the moment is to use net identity challenge that is based on PublicAddress. + if (source.NetAddress != "" || source.P2PNetAddress != "") && source.EnableP2PHybridMode && source.PublicAddress == "" { + return source, errors.New("PublicAddress must be specified when EnableP2PHybridMode is set") + } + source.PublicAddress = strings.ToLower(source.PublicAddress) + return source, nil } func loadConfig(reader io.Reader, config *Local) error { diff --git a/config/config_test.go b/config/config_test.go index 432c0f9281..20338766c3 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -121,6 +121,62 @@ func TestLocal_MergeConfig(t *testing.T) { require.Equal(t, c1.GossipFanout, c2.GossipFanout) } +func TestLocal_EnrichNetworkingConfig(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + c1 := Local{ + NetAddress: "test1", + GossipFanout: defaultLocal.GossipFanout, + } + c2, err := enrichNetworkingConfig(c1) + require.NoError(t, err) + require.NotEqual(t, c1, c2) + require.False(t, c1.EnableLedgerService) + require.False(t, c1.EnableBlockService) + require.Equal(t, c1.GossipFanout, defaultLocal.GossipFanout) + require.True(t, c2.EnableLedgerService) + require.True(t, c2.EnableBlockService) + require.Equal(t, c2.GossipFanout, defaultRelayGossipFanout) + + c1 = Local{ + EnableP2PHybridMode: true, + } + c2, err = enrichNetworkingConfig(c1) + require.NoError(t, err) + + c1 = Local{ + NetAddress: "test1", + EnableP2PHybridMode: true, + } + c2, err = enrichNetworkingConfig(c1) + require.ErrorContains(t, err, "PublicAddress must be specified when EnableP2PHybridMode is set") + + c1 = Local{ + P2PNetAddress: "test1", + EnableP2PHybridMode: true, + } + c2, err = enrichNetworkingConfig(c1) + require.ErrorContains(t, err, "PublicAddress must be specified when EnableP2PHybridMode is set") + + c1 = Local{ + EnableP2PHybridMode: true, + PublicAddress: "test2", + } + c2, err = enrichNetworkingConfig(c1) + require.NoError(t, err) + require.Equal(t, c1, c2) + require.True(t, c2.EnableP2PHybridMode) + require.NotEmpty(t, c2.PublicAddress) + + c1 = Local{ + PublicAddress: "R1.test3.my-domain.tld", + } + c2, err = enrichNetworkingConfig(c1) + require.NoError(t, err) + require.Equal(t, "r1.test3.my-domain.tld", c2.PublicAddress) +} + func saveFullPhonebook(phonebook phonebookBlackWhiteList, saveToDir string) error { filename := filepath.Join(saveToDir, PhonebookFilename) f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) @@ -559,9 +615,68 @@ func TestLocal_IsGossipServer(t *testing.T) { cfg := GetDefaultLocal() require.False(t, cfg.IsGossipServer()) + require.False(t, cfg.IsWsGossipServer()) + require.False(t, cfg.IsP2PGossipServer()) cfg.NetAddress = ":4160" require.True(t, cfg.IsGossipServer()) + require.True(t, cfg.IsWsGossipServer()) + require.False(t, cfg.IsP2PGossipServer()) + + cfg.EnableGossipService = false + // EnableGossipService does not matter + require.True(t, cfg.IsGossipServer()) + require.True(t, cfg.IsWsGossipServer()) + require.False(t, cfg.IsP2PGossipServer()) + + cfg.EnableP2P = true + cfg.NetAddress = ":4160" + require.True(t, cfg.IsGossipServer()) + require.False(t, cfg.IsWsGossipServer()) + require.True(t, cfg.IsP2PGossipServer()) + + cfg.EnableP2P = false + + cfg.EnableP2PHybridMode = true + // with net address set it is ws net gossip server + require.True(t, cfg.IsGossipServer()) + require.True(t, cfg.IsWsGossipServer()) + require.False(t, cfg.IsP2PGossipServer()) + + cfg.EnableP2PHybridMode = true + cfg.NetAddress = "" + require.False(t, cfg.IsGossipServer()) + require.False(t, cfg.IsWsGossipServer()) + require.False(t, cfg.IsP2PGossipServer()) + + cfg.EnableP2PHybridMode = true + cfg.P2PNetAddress = ":4190" + require.True(t, cfg.IsGossipServer()) + require.False(t, cfg.IsWsGossipServer()) + require.True(t, cfg.IsP2PGossipServer()) + + cfg.EnableP2PHybridMode = true + cfg.NetAddress = ":4160" + cfg.P2PNetAddress = ":4190" + require.True(t, cfg.IsGossipServer()) + require.True(t, cfg.IsWsGossipServer()) + require.True(t, cfg.IsP2PGossipServer()) + + cfg.EnableP2PHybridMode = true + cfg.EnableP2P = true + cfg.NetAddress = ":4160" + cfg.P2PNetAddress = ":4190" + require.True(t, cfg.IsGossipServer()) + require.True(t, cfg.IsWsGossipServer()) + require.True(t, cfg.IsP2PGossipServer()) + + cfg.EnableP2PHybridMode = true + cfg.EnableP2P = true + cfg.NetAddress = ":4160" + cfg.P2PNetAddress = "" + require.True(t, cfg.IsGossipServer()) + require.True(t, cfg.IsWsGossipServer()) + require.False(t, cfg.IsP2PGossipServer()) } func TestLocal_RecalculateConnectionLimits(t *testing.T) { @@ -569,45 +684,59 @@ func TestLocal_RecalculateConnectionLimits(t *testing.T) { t.Parallel() var tests = []struct { - maxFDs uint64 - reservedIn uint64 - restSoftIn uint64 - restHardIn uint64 - incomingIn int - - updated bool - restSoftExp uint64 - restHardExp uint64 - incomingExp int + maxFDs uint64 + reservedIn uint64 + restSoftIn uint64 + restHardIn uint64 + incomingIn int + p2pIncomingIn int + + updated bool + restSoftExp uint64 + restHardExp uint64 + incomingExp int + p2pIncomingExp int }{ - {100, 10, 20, 40, 50, false, 20, 40, 50}, // no change - {100, 10, 20, 50, 50, true, 20, 40, 50}, // borrow from rest - {100, 10, 25, 50, 50, true, 25, 40, 50}, // borrow from rest - {100, 10, 50, 50, 50, true, 40, 40, 50}, // borrow from rest, update soft - {100, 10, 9, 19, 81, true, 9, 10, 80}, // borrow from both rest and incoming - {100, 10, 10, 20, 80, true, 10, 10, 80}, // borrow from both rest and incoming - {100, 50, 10, 30, 40, true, 10, 10, 40}, // borrow from both rest and incoming - {100, 90, 10, 30, 40, true, 10, 10, 0}, // borrow from both rest and incoming, clear incoming - {4096, 256, 1024, 2048, 2400, true, 1024, 1440, 2400}, // real numbers - {5000, 256, 1024, 2048, 2400, false, 1024, 2048, 2400}, // real numbers + {100, 10, 20, 40, 50, 0, false, 20, 40, 50, 0}, // no change + {100, 10, 20, 50, 50, 0, true, 20, 40, 50, 0}, // borrow from rest + {100, 10, 25, 50, 50, 0, true, 25, 40, 50, 0}, // borrow from rest + {100, 10, 25, 50, 50, 50, true, 10, 10, 40, 40}, // borrow from rest for incoming and p2p incoming + {100, 10, 50, 50, 50, 0, true, 40, 40, 50, 0}, // borrow from rest, update soft + {100, 10, 50, 50, 40, 10, true, 40, 40, 40, 10}, // borrow from rest, update soft for incoming and p2p incoming + {100, 10, 9, 19, 81, 0, true, 9, 10, 80, 0}, // borrow from both rest and incoming + {100, 10, 9, 19, 41, 41, true, 9, 10, 40, 40}, // borrow from both rest and incoming for incoming and p2p incoming + {100, 90, 10, 30, 40, 0, true, 10, 10, 0, 0}, // borrow from both rest and incoming, clear incoming + {100, 90, 10, 30, 40, 40, true, 10, 10, 0, 0}, // borrow from both rest and incoming, clear incoming + {100, 90, 10, 30, 50, 40, true, 10, 10, 0, 0}, // borrow from both rest and incoming, clear incoming + {4096, 256, 1024, 2048, 2400, 0, true, 1024, 1440, 2400, 0}, // real numbers + {5000, 256, 1024, 2048, 2400, 0, false, 1024, 2048, 2400, 0}, // real numbers + {4096, 256, 1024, 2048, 2400, 1200, true, 240, 240, 2400, 1200}, // real numbers + {6000, 256, 1024, 2048, 2400, 1200, false, 1024, 2048, 2400, 1200}, // real numbers } for i, test := range tests { test := test - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("test=%d", i), func(t *testing.T) { t.Parallel() c := Local{ - RestConnectionsSoftLimit: test.restSoftIn, - RestConnectionsHardLimit: test.restHardIn, - IncomingConnectionsLimit: test.incomingIn, + NetAddress: ":4160", + RestConnectionsSoftLimit: test.restSoftIn, + RestConnectionsHardLimit: test.restHardIn, + IncomingConnectionsLimit: test.incomingIn, + P2PIncomingConnectionsLimit: test.p2pIncomingIn, + } + if test.p2pIncomingIn > 0 { + c.EnableP2PHybridMode = true + c.P2PNetAddress = ":4190" } - requireFDs := test.reservedIn + test.restHardIn + uint64(test.incomingIn) + requireFDs := test.reservedIn + test.restHardIn + uint64(test.incomingIn) + uint64(test.p2pIncomingIn) res := c.AdjustConnectionLimits(requireFDs, test.maxFDs) require.Equal(t, test.updated, res) - require.Equal(t, test.restSoftExp, c.RestConnectionsSoftLimit) - require.Equal(t, test.restHardExp, c.RestConnectionsHardLimit) - require.Equal(t, test.incomingExp, c.IncomingConnectionsLimit) + require.Equal(t, int(test.restSoftExp), int(c.RestConnectionsSoftLimit)) + require.Equal(t, int(test.restHardExp), int(c.RestConnectionsHardLimit)) + require.Equal(t, int(test.incomingExp), int(c.IncomingConnectionsLimit)) + require.Equal(t, int(test.p2pIncomingExp), int(c.P2PIncomingConnectionsLimit)) }) } } diff --git a/config/localTemplate.go b/config/localTemplate.go index 314b83a78b..9583a194cd 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -134,6 +134,8 @@ type Local struct { // Estimating 1.5MB per incoming connection, 1.5MB*2400 = 3.6GB IncomingConnectionsLimit int `version[0]:"-1" version[1]:"10000" version[17]:"800" version[27]:"2400"` + P2PIncomingConnectionsLimit int `version[34]:"1200"` + // BroadcastConnectionsLimit specifies the number of connections that // will receive broadcast (gossip) messages from this node. If the // node has more connections than this number, it will send broadcasts @@ -602,6 +604,7 @@ type Local struct { EnableP2P bool `version[31]:"false"` // EnableP2PHybridMode turns on both websockets and P2P networking. + // Enabling this setting also requires PublicAddress to be set. EnableP2PHybridMode bool `version[34]:"false"` // P2PNetAddress sets the listen address used for P2P networking, if hybrid mode is set. @@ -734,10 +737,21 @@ func (cfg Local) TxFilterCanonicalEnabled() bool { return cfg.TxIncomingFilteringFlags&txFilterCanonical != 0 } -// IsGossipServer returns true if NetAddress is set and this node supposed -// to start websocket server +// IsGossipServer returns true if this node supposed to start websocket or p2p server func (cfg Local) IsGossipServer() bool { - return cfg.NetAddress != "" + return cfg.IsWsGossipServer() || cfg.IsP2PGossipServer() +} + +// IsWsGossipServer returns true if a node configured to run a listening ws net +func (cfg Local) IsWsGossipServer() bool { + // 1. NetAddress is set and EnableP2P is not set + // 2. NetAddress is set and EnableP2PHybridMode is set then EnableP2P is overridden by EnableP2PHybridMode + return cfg.NetAddress != "" && (!cfg.EnableP2P || cfg.EnableP2PHybridMode) +} + +// IsP2PGossipServer returns true if a node configured to run a listening p2p net +func (cfg Local) IsP2PGossipServer() bool { + return (cfg.EnableP2P && !cfg.EnableP2PHybridMode && cfg.NetAddress != "") || (cfg.EnableP2PHybridMode && cfg.P2PNetAddress != "") } // ensureAbsGenesisDir will convert a path to absolute, and will attempt to make a genesis directory there @@ -935,10 +949,24 @@ func (cfg *Local) AdjustConnectionLimits(requiredFDs, maxFDs uint64) bool { if cfg.RestConnectionsHardLimit <= diff+reservedRESTConns { restDelta := diff + reservedRESTConns - cfg.RestConnectionsHardLimit cfg.RestConnectionsHardLimit = reservedRESTConns - if cfg.IncomingConnectionsLimit > int(restDelta) { - cfg.IncomingConnectionsLimit -= int(restDelta) - } else { - cfg.IncomingConnectionsLimit = 0 + splitRatio := 1 + if cfg.IsWsGossipServer() && cfg.IsP2PGossipServer() { + // split the rest of the delta between ws and p2p evenly + splitRatio = 2 + } + if cfg.IsWsGossipServer() { + if cfg.IncomingConnectionsLimit > int(restDelta) { + cfg.IncomingConnectionsLimit -= int(restDelta) / splitRatio + } else { + cfg.IncomingConnectionsLimit = 0 + } + } + if cfg.IsP2PGossipServer() { + if cfg.P2PIncomingConnectionsLimit > int(restDelta) { + cfg.P2PIncomingConnectionsLimit -= int(restDelta) / splitRatio + } else { + cfg.P2PIncomingConnectionsLimit = 0 + } } } else { cfg.RestConnectionsHardLimit -= diff diff --git a/config/local_defaults.go b/config/local_defaults.go index ae2ed22ebf..57457531be 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -119,6 +119,7 @@ var defaultLocal = Local{ OptimizeAccountsDatabaseOnStartup: false, OutgoingMessageFilterBucketCount: 3, OutgoingMessageFilterBucketSize: 128, + P2PIncomingConnectionsLimit: 1200, P2PNetAddress: "", P2PPersistPeerID: false, P2PPrivateKeyLocation: "", diff --git a/daemon/algod/server.go b/daemon/algod/server.go index c43b0b0693..309fdc5799 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -149,9 +149,21 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes if cfg.IsGossipServer() { var ot basics.OverflowTracker - fdRequired = ot.Add(fdRequired, uint64(cfg.IncomingConnectionsLimit)+network.ReservedHealthServiceConnections) + fdRequired = ot.Add(fdRequired, network.ReservedHealthServiceConnections) if ot.Overflowed { - return errors.New("Initialize() overflowed when adding up IncomingConnectionsLimit to the existing RLIMIT_NOFILE value; decrease RestConnectionsHardLimit or IncomingConnectionsLimit") + return errors.New("Initialize() overflowed when adding up ReservedHealthServiceConnections to the existing RLIMIT_NOFILE value; decrease RestConnectionsHardLimit") + } + if cfg.IsWsGossipServer() { + fdRequired = ot.Add(fdRequired, uint64(cfg.IncomingConnectionsLimit)) + if ot.Overflowed { + return errors.New("Initialize() overflowed when adding up IncomingConnectionsLimit to the existing RLIMIT_NOFILE value; decrease IncomingConnectionsLimit") + } + } + if cfg.IsP2PGossipServer() { + fdRequired = ot.Add(fdRequired, uint64(cfg.P2PIncomingConnectionsLimit)) + if ot.Overflowed { + return errors.New("Initialize() overflowed when adding up P2PIncomingConnectionsLimit to the existing RLIMIT_NOFILE value; decrease P2PIncomingConnectionsLimit") + } } _, hard, fdErr := util.GetFdLimits() if fdErr != nil { @@ -164,14 +176,18 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes // but try to keep cfg.ReservedFDs untouched by decreasing other limits if cfg.AdjustConnectionLimits(fdRequired, hard) { s.log.Warnf( - "Updated connection limits: RestConnectionsSoftLimit=%d, RestConnectionsHardLimit=%d, IncomingConnectionsLimit=%d", + "Updated connection limits: RestConnectionsSoftLimit=%d, RestConnectionsHardLimit=%d, IncomingConnectionsLimit=%d, P2PIncomingConnectionsLimit=%d", cfg.RestConnectionsSoftLimit, cfg.RestConnectionsHardLimit, cfg.IncomingConnectionsLimit, + cfg.P2PIncomingConnectionsLimit, ) - if cfg.IncomingConnectionsLimit == 0 { + if cfg.IsWsGossipServer() && cfg.IncomingConnectionsLimit == 0 { return errors.New("Initialize() failed to adjust connection limits") } + if cfg.IsP2PGossipServer() && cfg.P2PIncomingConnectionsLimit == 0 { + return errors.New("Initialize() failed to adjust p2p connection limits") + } } } fdErr = util.SetFdSoftLimit(maxFDs) diff --git a/installer/config.json.example b/installer/config.json.example index 7f16155303..3a9714bbfb 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -98,6 +98,7 @@ "OptimizeAccountsDatabaseOnStartup": false, "OutgoingMessageFilterBucketCount": 3, "OutgoingMessageFilterBucketSize": 128, + "P2PIncomingConnectionsLimit": 1200, "P2PNetAddress": "", "P2PPersistPeerID": false, "P2PPrivateKeyLocation": "", diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go index 25de422026..ce72071ff0 100644 --- a/netdeploy/remote/deployedNetwork.go +++ b/netdeploy/remote/deployedNetwork.go @@ -1005,6 +1005,16 @@ func createHostSpec(host HostConfig, template cloudHost) (hostSpec cloudHostSpec portList = append(portList, strconv.Itoa(port)) } } + if node.P2PNetAddress != "" { + port, err = extractPublicPort(node.P2PNetAddress) + if err != nil { + return + } + if !ports[port] { + ports[port] = true + portList = append(portList, strconv.Itoa(port)) + } + } // See if the APIEndpoint is open to the public, and if so add it // Error means it's not valid/specified as public port diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index 4880d76eb9..bd4b63dac8 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -35,6 +35,8 @@ type NodeConfig struct { DeadlockOverride int `json:",omitempty"` // -1 = Disable deadlock detection, 0 = Use Default for build, 1 = Enable ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete P2PBootstrap bool // True if this node should be a p2p bootstrap node and registered in DNS + P2PNetAddress string `json:",omitempty"` + PublicAddress bool // NodeNameMatchRegex is tested against Name in generated configs and if matched the rest of the configs in this record are applied as a template NodeNameMatchRegex string `json:",omitempty"` diff --git a/netdeploy/remote/nodecfg/nodeConfigurator.go b/netdeploy/remote/nodecfg/nodeConfigurator.go index 842570bfc8..8e6bea9718 100644 --- a/netdeploy/remote/nodecfg/nodeConfigurator.go +++ b/netdeploy/remote/nodecfg/nodeConfigurator.go @@ -93,6 +93,10 @@ func (nc *nodeConfigurator) apply(rootConfigDir, rootNodeDir string) (err error) nc.genesisFile = filepath.Join(rootConfigDir, "genesisdata", config.GenesisJSONFile) nc.genesisData, err = bookkeeping.LoadGenesisFromFile(nc.genesisFile) + if err != nil { + return fmt.Errorf("error loading genesis from '%s': %v", nc.genesisFile, err) + + } nodeDirs, err := nc.prepareNodeDirs(nc.config.Nodes, rootConfigDir, rootNodeDir) if err != nil { return fmt.Errorf("error preparing node directories: %v", err) @@ -198,6 +202,11 @@ func (nc *nodeConfigurator) prepareNodeDirs(configs []remote.NodeConfig, rootCon return } +// getHostName creates a DNS name for a host +func (nc *nodeConfigurator) getNetworkHostName() string { + return nc.config.Name + "." + string(nc.genesisData.Network) + ".algodev.network" +} + func (nc *nodeConfigurator) registerDNSRecords() (err error) { cfZoneID, cfToken, err := getClouldflareCredentials() if err != nil { @@ -210,12 +219,13 @@ func (nc *nodeConfigurator) registerDNSRecords() (err error) { const weight = 1 const relayBootstrap = "_algobootstrap" const metricsSrv = "_metrics" + const tcpProto = "_tcp" const proxied = false // If we need to register anything, first register a DNS entry // to map our network DNS name to our public name (or IP) provided to nodecfg // Network HostName = eg r1.testnet.algodev.network - networkHostName := nc.config.Name + "." + string(nc.genesisData.Network) + ".algodev.network" + networkHostName := nc.getNetworkHostName() isIP := net.ParseIP(nc.dnsName) != nil var recordType string if isIP { @@ -232,9 +242,10 @@ func (nc *nodeConfigurator) registerDNSRecords() (err error) { if parseErr != nil { return parseErr } - fmt.Fprintf(os.Stdout, "...... Adding Relay SRV Record '%s' -> '%s' .\n", entry.srvName, networkHostName) + fmt.Fprintf(os.Stdout, "...... Adding Relay SRV Record [%s.%s] '%s' [%d %d] -> '%s' .\n", + relayBootstrap, tcpProto, entry.srvName, priority, port, networkHostName) err = cloudflareDNS.SetSRVRecord(context.Background(), entry.srvName, networkHostName, - cloudflare.AutomaticTTL, priority, uint(port), relayBootstrap, "_tcp", weight) + cloudflare.AutomaticTTL, priority, uint(port), relayBootstrap, tcpProto, weight) if err != nil { return } @@ -246,9 +257,10 @@ func (nc *nodeConfigurator) registerDNSRecords() (err error) { fmt.Fprintf(os.Stdout, "Error parsing port for srv record: %s (port %v)\n", parseErr, entry) return parseErr } - fmt.Fprintf(os.Stdout, "...... Adding Metrics SRV Record '%s' -> '%s' .\n", entry.srvName, networkHostName) + fmt.Fprintf(os.Stdout, "...... Adding Metrics SRV Record [%s.%s] '%s' [%d %d] -> '%s' .\n", + metricsSrv, tcpProto, entry.srvName, priority, port, networkHostName) err = cloudflareDNS.SetSRVRecord(context.Background(), entry.srvName, networkHostName, - cloudflare.AutomaticTTL, priority, uint(port), metricsSrv, "_tcp", weight) + cloudflare.AutomaticTTL, priority, uint(port), metricsSrv, tcpProto, weight) if err != nil { fmt.Fprintf(os.Stdout, "Error creating srv record: %s (%v)\n", err, entry) return diff --git a/netdeploy/remote/nodecfg/nodeDir.go b/netdeploy/remote/nodecfg/nodeDir.go index bdfc037438..304fa4c636 100644 --- a/netdeploy/remote/nodecfg/nodeDir.go +++ b/netdeploy/remote/nodecfg/nodeDir.go @@ -104,6 +104,11 @@ func (nd *nodeDir) configure() (err error) { return } + if err = nd.configurePublicAddress(nd.PublicAddress); err != nil { + fmt.Fprintf(os.Stdout, "Error during configurePublicAddress: %s\n", err) + return + } + if err = nd.configureP2PDNSBootstrap(nd.P2PBootstrap); err != nil { fmt.Fprintf(os.Stdout, "Error during configureP2PDNSBootstrap: %s\n", err) return @@ -155,15 +160,46 @@ func (nd *nodeDir) configureNetAddress() (err error) { fmt.Fprintf(os.Stdout, " - Assigning NetAddress: %s\n", nd.NetAddress) nd.config.NetAddress = nd.NetAddress if nd.IsRelay() && nd.NetAddress[0] == ':' { - fmt.Fprintf(os.Stdout, " - adding to relay addresses\n") - for _, bootstrapRecord := range nd.config.DNSBootstrapArray(nd.configurator.genesisData.Network) { - nd.configurator.addRelaySrv(bootstrapRecord.PrimarySRVBootstrap, nd.NetAddress) + if nd.config.EnableP2P && !nd.config.EnableP2PHybridMode { + fmt.Fprintf(os.Stdout, " - skipping relay addresses - p2p mode\n") + } else { + fmt.Fprintf(os.Stdout, " - adding to relay addresses\n") + for _, bootstrapRecord := range nd.config.DNSBootstrapArray(nd.configurator.genesisData.Network) { + nd.configurator.addRelaySrv(bootstrapRecord.PrimarySRVBootstrap, nd.NetAddress) + } } } + if nd.P2PNetAddress != "" { + fmt.Fprintf(os.Stdout, " - Assigning P2PNetAddress: %s\n", nd.P2PNetAddress) + nd.config.P2PNetAddress = nd.P2PNetAddress + } err = nd.saveConfig() return } +func (nd *nodeDir) configurePublicAddress(publicAddress bool) error { + if !publicAddress { + return nil + } + if !nd.IsRelay() { + return errors.New("publicAddress is only valid for relay nodes") + } + if nd.config.EnableP2P && !nd.config.EnableP2PHybridMode { + return errors.New("publicAddress is only valid websocket gossip node or a hybrid mode node") + } + + if err := nd.ensureConfig(); err != nil { + return err + } + + if nd.NetAddress[0] == ':' { + networkHostName := nd.configurator.getNetworkHostName() + nd.NetAddress + nd.config.PublicAddress = strings.ToLower(networkHostName) + fmt.Fprintf(os.Stdout, " - Assigning PublicAddress: %s\n", networkHostName) + } + return nd.saveConfig() +} + func (nd *nodeDir) configureP2PDNSBootstrap(p2pBootstrap bool) error { if !p2pBootstrap { return nil @@ -179,7 +215,7 @@ func (nd *nodeDir) configureP2PDNSBootstrap(p2pBootstrap bool) error { if !nd.config.EnableP2P && !nd.config.EnableP2PHybridMode { return errors.New("p2p bootstrap requires EnableP2P or EnableP2PHybridMode to be set") } - if nd.NetAddress == "" && nd.config.P2PNetAddress == "" { + if nd.NetAddress == "" && nd.P2PNetAddress == "" { return errors.New("p2p bootstrap requires NetAddress or P2PNetAddress to be set") } if !nd.config.EnableGossipService { @@ -187,8 +223,8 @@ func (nd *nodeDir) configureP2PDNSBootstrap(p2pBootstrap bool) error { } netAddress := nd.NetAddress - if nd.config.P2PNetAddress != "" { - netAddress = nd.config.P2PNetAddress + if nd.P2PNetAddress != "" { + netAddress = nd.P2PNetAddress } key, err := p2p.GetPrivKey(config.Local{P2PPersistPeerID: true}, nd.dataDir) diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go index 27fc6edbb0..d30e03cee2 100644 --- a/network/hybridNetwork.go +++ b/network/hybridNetwork.go @@ -42,11 +42,17 @@ func NewHybridP2PNetwork(log logging.Logger, cfg config.Local, datadir string, p // supply alternate NetAddress for P2P network p2pcfg := cfg p2pcfg.NetAddress = cfg.P2PNetAddress - p2pnet, err := NewP2PNetwork(log, p2pcfg, datadir, phonebookAddresses, genesisID, networkID, nodeInfo) + identityTracker := NewIdentityTracker() + p2pnet, err := NewP2PNetwork(log, p2pcfg, datadir, phonebookAddresses, genesisID, networkID, nodeInfo, &identityOpts{tracker: identityTracker}) if err != nil { return nil, err } - wsnet, err := NewWebsocketNetwork(log, cfg, phonebookAddresses, genesisID, networkID, nodeInfo, p2pnet.PeerID(), p2pnet.PeerIDSigner()) + + identOpts := identityOpts{ + tracker: identityTracker, + scheme: NewIdentityChallengeScheme(NetIdentityDedupNames(cfg.PublicAddress, p2pnet.PeerID().String()), NetIdentitySigner(p2pnet.PeerIDSigner())), + } + wsnet, err := NewWebsocketNetwork(log, cfg, phonebookAddresses, genesisID, networkID, nodeInfo, &identOpts) if err != nil { return nil, err } diff --git a/network/hybridNetwork_test.go b/network/hybridNetwork_test.go new file mode 100644 index 0000000000..7c76c1e38e --- /dev/null +++ b/network/hybridNetwork_test.go @@ -0,0 +1,183 @@ +// Copyright (C) 2019-2024 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 network + +import ( + "net/url" + "testing" + "time" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +// TestHybridNetwork_DuplicateConn checks the same nodes do not connect over ws and p2p. +// Scenario: +// 1. Create a hybrid network: relay and two nodes +// 2. Let them connect to the relay +// 3. Ensure relay has only two connections +// 4. Ensure extra connection attempts were rejected by nodes rather than relay +func TestHybridNetwork_DuplicateConn(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + cfg.EnableP2PHybridMode = true + log := logging.TestingLog(t) + const p2pKeyDir = "" + + identDiscValue := networkPeerIdentityDisconnect.GetUint64Value() + + relayCfg := cfg + relayCfg.ForceRelayMessages = true + netA, err := NewHybridP2PNetwork(log.With("node", "netA"), relayCfg, p2pKeyDir, nil, genesisID, "net", &nopeNodeInfo{}) + require.NoError(t, err) + + err = netA.Start() + require.NoError(t, err) + + // collect ws address + addr, portListen := netA.wsNetwork.Address() + require.True(t, portListen) + require.NotZero(t, addr) + parsed, err := url.Parse(addr) + require.NoError(t, err) + addr = parsed.Host + netA.Stop() + + // make it net address and restart the node + relayCfg.NetAddress = addr + relayCfg.PublicAddress = addr + relayCfg.P2PNetAddress = ":0" + netA, err = NewHybridP2PNetwork(log.With("node", "netA"), relayCfg, p2pKeyDir, nil, genesisID, "net", &nopeNodeInfo{}) + require.NoError(t, err) + + err = netA.Start() + require.NoError(t, err) + defer netA.Stop() + + // collect relay address and prepare nodes phonebook + peerInfoA := netA.p2pNetwork.service.AddrInfo() + addrsAp2p, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsAp2p[0]) + multiAddrStr := addrsAp2p[0].String() + + fullAddr, portListen := netA.wsNetwork.Address() + require.True(t, portListen) + require.NotZero(t, addr) + require.Contains(t, fullAddr, addr) + + phoneBookAddresses := []string{multiAddrStr, addr} + + netB, err := NewHybridP2PNetwork(log.With("node", "netB"), cfg, "", phoneBookAddresses, genesisID, "net", &nopeNodeInfo{}) + require.NoError(t, err) + // for netB start the p2p network first + err = netB.p2pNetwork.Start() + require.NoError(t, err) + defer netB.Stop() + + netC, err := NewHybridP2PNetwork(log.With("node", "netC"), cfg, "", phoneBookAddresses, genesisID, "net", &nopeNodeInfo{}) + require.NoError(t, err) + // for netC start the ws network first + err = netC.wsNetwork.Start() + require.NoError(t, err) + defer netC.Stop() + + // ensure initial connections are done + require.Eventually(t, func() bool { + return len(netA.GetPeers(PeersConnectedIn)) == 2 + }, 3*time.Second, 50*time.Millisecond) + + // start the second half of the hybrid net + err = netB.wsNetwork.Start() + require.NoError(t, err) + err = netC.p2pNetwork.Start() + require.NoError(t, err) + + // wait for connection attempts. nodes need some time to make connections, + // and instead of `time.Sleep(1 * time.Second)` the networkPeerIdentityDisconnect net identity counter is used. + // Since this test is not parallel the networkPeerIdentityDisconnect should not be modified from outside. + // Both netB and netC are attempting to connect but netA could also open an outgoing stream in netB or netC connection. + // So, the counter should be at least 2+identDiscValue. + const waitFor = 3 * time.Second + const checkEvery = 50 * time.Millisecond + const maxTicks = int(waitFor / checkEvery) + const debugThreshold = maxTicks - maxTicks/20 // log last 5% of ticks + require.Greater(t, debugThreshold, 1) + require.Less(t, debugThreshold, maxTicks) + tickCounter := 0 + require.Eventually(t, func() bool { + if tickCounter >= debugThreshold { + log.Infof("networkPeerIdentityDisconnect: %d\n", networkPeerIdentityDisconnect.GetUint64Value()) + } + tickCounter++ + return networkPeerIdentityDisconnect.GetUint64Value() >= 2+identDiscValue + }, waitFor, checkEvery) + + // now count connections + // netA should have 2 connections, not 4 + // netB should have 1 connection (via p2p) + // netC should have 1 connection (via ws) + + tickCounter = 0 + require.Eventually(t, func() bool { + if tickCounter >= debugThreshold { + netAIn := len(netA.GetPeers(PeersConnectedIn)) + netAOut := len(netA.GetPeers(PeersConnectedOut)) + netBIn := len(netB.GetPeers(PeersConnectedIn)) + netBOut := len(netB.GetPeers(PeersConnectedOut)) + netCIn := len(netC.GetPeers(PeersConnectedIn)) + netCOut := len(netC.GetPeers(PeersConnectedOut)) + log.Infof("netA in/out: %d/%d, netB in/out: %d/%d, netC in/out: %d/%d\n", netAIn, netAOut, netBIn, netBOut, netCIn, netCOut) + } + tickCounter++ + return len(netB.GetPeers(PeersConnectedOut)) == 1 + }, waitFor, checkEvery) + + tickCounter = 0 + require.Eventually(t, func() bool { + if tickCounter >= debugThreshold { + netAIn := len(netA.GetPeers(PeersConnectedIn)) + netAOut := len(netA.GetPeers(PeersConnectedOut)) + netBIn := len(netB.GetPeers(PeersConnectedIn)) + netBOut := len(netB.GetPeers(PeersConnectedOut)) + netCIn := len(netC.GetPeers(PeersConnectedIn)) + netCOut := len(netC.GetPeers(PeersConnectedOut)) + log.Infof("netA in/out: %d/%d, netB in/out: %d/%d, netC in/out: %d/%d\n", netAIn, netAOut, netBIn, netBOut, netCIn, netCOut) + } + tickCounter++ + return len(netC.GetPeers(PeersConnectedOut)) == 1 + }, waitFor, checkEvery) + + tickCounter = 0 + require.Eventually(t, func() bool { + if tickCounter >= debugThreshold { + netAIn := len(netA.GetPeers(PeersConnectedIn)) + netAOut := len(netA.GetPeers(PeersConnectedOut)) + netBIn := len(netB.GetPeers(PeersConnectedIn)) + netBOut := len(netB.GetPeers(PeersConnectedOut)) + netCIn := len(netC.GetPeers(PeersConnectedIn)) + netCOut := len(netC.GetPeers(PeersConnectedOut)) + log.Infof("netA in/out: %d/%d, netB in/out: %d/%d, netC in/out: %d/%d\n", netAIn, netAOut, netBIn, netBOut, netCIn, netCOut) + } + tickCounter++ + return len(netA.GetPeers(PeersConnectedIn)) == 2 + }, 3*time.Second, 50*time.Millisecond) +} diff --git a/network/netidentity.go b/network/netidentity.go index 4d797a1a5b..30755f0648 100644 --- a/network/netidentity.go +++ b/network/netidentity.go @@ -100,6 +100,11 @@ type identityChallengeSigner interface { PublicKey() crypto.PublicKey } +type identityOpts struct { + scheme identityChallengeScheme + tracker identityTracker +} + type identityChallengeLegacySigner struct { keys *crypto.SignatureSecrets } @@ -120,37 +125,81 @@ func (s *identityChallengeLegacySigner) PublicKey() crypto.PublicKey { // exchanging and verifying public key challenges and attaching them to headers, // or returning the message payload to be sent type identityChallengePublicKeyScheme struct { - dedupName string + dedupNames map[string]struct{} identityKeys identityChallengeSigner } +type identityChallengeSchemeConfig struct { + dedupNames []string + signer identityChallengeSigner +} + +// IdentityChallengeSchemeOption is a function that can be passed to NewIdentityChallengeScheme +type IdentityChallengeSchemeOption func(*identityChallengeSchemeConfig) + +// NetIdentityDedupNames is an option to set the deduplication names for the identity challenge scheme +func NetIdentityDedupNames(dn ...string) IdentityChallengeSchemeOption { + return func(c *identityChallengeSchemeConfig) { + c.dedupNames = append(c.dedupNames, dn...) + } +} + +// NetIdentitySigner is an option to set the signer for the identity challenge scheme +func NetIdentitySigner(s identityChallengeSigner) IdentityChallengeSchemeOption { + return func(c *identityChallengeSchemeConfig) { + c.signer = s + } +} + // NewIdentityChallengeScheme will create a default Identification Scheme -func NewIdentityChallengeScheme(dn string) *identityChallengePublicKeyScheme { - // without an deduplication name, there is no identityto manage, so just return an empty scheme - if dn == "" { +func NewIdentityChallengeScheme(opts ...IdentityChallengeSchemeOption) *identityChallengePublicKeyScheme { + // without an deduplication name, there is no identity to manage, so just return an empty scheme + if len(opts) == 0 { + return &identityChallengePublicKeyScheme{} + } + + config := identityChallengeSchemeConfig{} + for _, opt := range opts { + opt(&config) + } + + if len(config.dedupNames) == 0 { return &identityChallengePublicKeyScheme{} } + hasNonEmpty := false + dedupNames := make(map[string]struct{}, len(config.dedupNames)) + for _, name := range config.dedupNames { + if len(name) > 0 { + dedupNames[name] = struct{}{} + hasNonEmpty = true + } + } + if !hasNonEmpty { + return &identityChallengePublicKeyScheme{} + } + + if config.signer != nil { + return &identityChallengePublicKeyScheme{ + dedupNames: dedupNames, + identityKeys: config.signer, + } + } + var seed crypto.Seed crypto.RandBytes(seed[:]) - return &identityChallengePublicKeyScheme{ - dedupName: dn, + dedupNames: dedupNames, identityKeys: &identityChallengeLegacySigner{keys: crypto.GenerateSignatureSecrets(seed)}, } } -// NewIdentityChallengeSchemeWithSigner will create an identification Scheme with a given signer -func NewIdentityChallengeSchemeWithSigner(dn string, signer identityChallengeSigner) *identityChallengePublicKeyScheme { - return &identityChallengePublicKeyScheme{dedupName: dn, identityKeys: signer} -} - // AttachChallenge will generate a new identity challenge and will encode and attach the challenge // as a header. It returns the identityChallengeValue used for this challenge, so the network can // confirm it later (by passing it to VerifyResponse), or returns an empty challenge if dedupName is // not set. func (i identityChallengePublicKeyScheme) AttachChallenge(attachTo http.Header, addr string) identityChallengeValue { - if i.dedupName == "" || addr == "" { + if len(i.dedupNames) == 0 || addr == "" { return identityChallengeValue{} } c := identityChallenge{ @@ -172,7 +221,7 @@ func (i identityChallengePublicKeyScheme) AttachChallenge(attachTo http.Header, // or returns empty values if the header did not end up getting set func (i identityChallengePublicKeyScheme) VerifyRequestAndAttachResponse(attachTo http.Header, h http.Header) (identityChallengeValue, crypto.PublicKey, error) { // if dedupName is not set, this scheme is not configured to exchange identity - if i.dedupName == "" { + if len(i.dedupNames) == 0 { return identityChallengeValue{}, crypto.PublicKey{}, nil } // if the headerString is not populated, the peer isn't participating in identity exchange @@ -193,10 +242,11 @@ func (i identityChallengePublicKeyScheme) VerifyRequestAndAttachResponse(attachT if !idChal.Verify() { return identityChallengeValue{}, crypto.PublicKey{}, fmt.Errorf("identity challenge incorrectly signed") } + // if the address is not meant for this host, return without attaching headers, // but also do not emit an error. This is because if an operator were to incorrectly // specify their dedupName, it could result in inappropriate disconnections from valid peers - if string(idChal.Msg.PublicAddress) != i.dedupName { + if _, ok := i.dedupNames[string(idChal.Msg.PublicAddress)]; !ok { return identityChallengeValue{}, crypto.PublicKey{}, nil } // make the response object, encode it and attach it to the header @@ -216,7 +266,7 @@ func (i identityChallengePublicKeyScheme) VerifyRequestAndAttachResponse(attachT // encoded identityVerificationMessage to send to the peer. Otherwise, it returns empty values. func (i identityChallengePublicKeyScheme) VerifyResponse(h http.Header, c identityChallengeValue) (crypto.PublicKey, []byte, error) { // if we are not participating in identity challenge exchange, do nothing (no error and no value) - if i.dedupName == "" { + if len(i.dedupNames) == 0 { return crypto.PublicKey{}, []byte{}, nil } headerString := h.Get(IdentityChallengeHeader) @@ -400,9 +450,16 @@ type identityTracker interface { setIdentity(p *wsPeer) bool } +// noopIdentityTracker implements identityTracker by doing nothing. +// Intended for pure p2p mode when libp2p is handling identities itself. +type noopIdentityTracker struct{} + +func (noopIdentityTracker) setIdentity(p *wsPeer) bool { return true } +func (noopIdentityTracker) removeIdentity(p *wsPeer) {} + // publicKeyIdentTracker implements identityTracker by // mapping from PublicKeys exchanged in identity challenges to a peer -// this structure is not thread-safe; it is protected by wn.peersLock. +// this structure is not thread-safe; it is protected by wn.peersLock or p2p.wsPeersLock type publicKeyIdentTracker struct { peersByID map[crypto.PublicKey]*wsPeer } diff --git a/network/netidentity_test.go b/network/netidentity_test.go index f87480c1b1..a54628a6fe 100644 --- a/network/netidentity_test.go +++ b/network/netidentity_test.go @@ -32,12 +32,15 @@ func TestIdentityChallengeSchemeAttachIfEnabled(t *testing.T) { partitiontest.PartitionTest(t) h := http.Header{} - i := NewIdentityChallengeScheme("") + i0 := NewIdentityChallengeScheme() + i := NewIdentityChallengeScheme(NetIdentityDedupNames("")) + require.Equal(t, i0, i) + require.Zero(t, *i) chal := i.AttachChallenge(h, "other") - require.Empty(t, h.Get(IdentityChallengeHeader)) - require.Empty(t, chal) + require.Zero(t, h.Get(IdentityChallengeHeader)) + require.Zero(t, chal) - j := NewIdentityChallengeScheme("yes") + j := NewIdentityChallengeScheme(NetIdentityDedupNames("yes")) chal = j.AttachChallenge(h, "other") require.NotEmpty(t, h.Get(IdentityChallengeHeader)) require.NotEmpty(t, chal) @@ -48,7 +51,7 @@ func TestIdentityChallengeSchemeAttachIfEnabled(t *testing.T) { func TestIdentityChallengeSchemeVerifyRequestAndAttachResponse(t *testing.T) { partitiontest.PartitionTest(t) - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) // author a challenge to the other scheme h := http.Header{} i.AttachChallenge(h, "i2") @@ -58,7 +61,7 @@ func TestIdentityChallengeSchemeVerifyRequestAndAttachResponse(t *testing.T) { h = http.Header{} i.AttachChallenge(h, "i2") r := http.Header{} - i2 := NewIdentityChallengeScheme("") + i2 := NewIdentityChallengeScheme() chal, key, err := i2.VerifyRequestAndAttachResponse(r, h) require.Empty(t, r.Get(IdentityChallengeHeader)) require.Empty(t, chal) @@ -69,7 +72,7 @@ func TestIdentityChallengeSchemeVerifyRequestAndAttachResponse(t *testing.T) { h = http.Header{} i.AttachChallenge(h, "i2") r = http.Header{} - i2 = NewIdentityChallengeScheme("not i2") + i2 = NewIdentityChallengeScheme(NetIdentityDedupNames("not i2")) chal, key, err = i2.VerifyRequestAndAttachResponse(r, h) require.Empty(t, r.Get(IdentityChallengeHeader)) require.Empty(t, chal) @@ -80,7 +83,7 @@ func TestIdentityChallengeSchemeVerifyRequestAndAttachResponse(t *testing.T) { h = http.Header{} h.Add(IdentityChallengeHeader, "garbage") r = http.Header{} - i2 = NewIdentityChallengeScheme("i2") + i2 = NewIdentityChallengeScheme(NetIdentityDedupNames("i2")) chal, key, err = i2.VerifyRequestAndAttachResponse(r, h) require.Empty(t, r.Get(IdentityChallengeHeader)) require.Empty(t, chal) @@ -91,7 +94,7 @@ func TestIdentityChallengeSchemeVerifyRequestAndAttachResponse(t *testing.T) { h = http.Header{} i.AttachChallenge(h, "i2") r = http.Header{} - i2 = NewIdentityChallengeScheme("i2") + i2 = NewIdentityChallengeScheme(NetIdentityDedupNames("i2")) chal, key, err = i2.VerifyRequestAndAttachResponse(r, h) require.NotEmpty(t, r.Get(IdentityChallengeHeader)) require.NotEmpty(t, chal) @@ -103,11 +106,11 @@ func TestIdentityChallengeNoErrorWhenNotParticipating(t *testing.T) { partitiontest.PartitionTest(t) // blank deduplication name will make the scheme a no-op - iNotParticipate := NewIdentityChallengeScheme("") + iNotParticipate := NewIdentityChallengeScheme() // create a request header first h := http.Header{} - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) origChal := i.AttachChallenge(h, "i1") require.NotEmpty(t, h.Get(IdentityChallengeHeader)) require.NotEmpty(t, origChal) @@ -120,7 +123,7 @@ func TestIdentityChallengeNoErrorWhenNotParticipating(t *testing.T) { // create a response h2 := http.Header{} - i2 := NewIdentityChallengeScheme("i2") + i2 := NewIdentityChallengeScheme(NetIdentityDedupNames("i2")) i2.VerifyRequestAndAttachResponse(h2, h) // confirm a nil scheme will not return values or error @@ -148,7 +151,7 @@ func TestIdentityChallengeSchemeVerifyResponse(t *testing.T) { partitiontest.PartitionTest(t) h := http.Header{} - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) // author a challenge to ourselves origChal := i.AttachChallenge(h, "i1") require.NotEmpty(t, h.Get(IdentityChallengeHeader)) @@ -176,7 +179,7 @@ func TestIdentityChallengeSchemeBadSignature(t *testing.T) { partitiontest.PartitionTest(t) h := http.Header{} - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) // Copy the logic of attaching the header and signing so we can sign it wrong c := identityChallengeSigned{ Msg: identityChallenge{ @@ -204,7 +207,7 @@ func TestIdentityChallengeSchemeBadPayload(t *testing.T) { partitiontest.PartitionTest(t) h := http.Header{} - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) h.Add(IdentityChallengeHeader, "NOT VALID BASE 64! :)") // observe that VerifyRequestAndAttachResponse won't do anything on bad signature @@ -222,7 +225,7 @@ func TestIdentityChallengeSchemeBadResponseSignature(t *testing.T) { partitiontest.PartitionTest(t) h := http.Header{} - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) // author a challenge to ourselves origChal := i.AttachChallenge(h, "i1") require.NotEmpty(t, h.Get(IdentityChallengeHeader)) @@ -253,7 +256,7 @@ func TestIdentityChallengeSchemeBadResponsePayload(t *testing.T) { partitiontest.PartitionTest(t) h := http.Header{} - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) // author a challenge to ourselves origChal := i.AttachChallenge(h, "i1") require.NotEmpty(t, h.Get(IdentityChallengeHeader)) @@ -275,7 +278,7 @@ func TestIdentityChallengeSchemeWrongChallenge(t *testing.T) { partitiontest.PartitionTest(t) h := http.Header{} - i := NewIdentityChallengeScheme("i1") + i := NewIdentityChallengeScheme(NetIdentityDedupNames("i1")) // author a challenge to ourselves origChal := i.AttachChallenge(h, "i1") require.NotEmpty(t, h.Get(IdentityChallengeHeader)) @@ -366,3 +369,67 @@ func TestIdentityTrackerHandlerGuard(t *testing.T) { } require.Equal(t, OutgoingMessage{}, identityVerificationHandler(msg)) } + +// TestNewIdentityChallengeScheme ensures NewIdentityChallengeScheme returns +// a correct identityChallengePublicKeyScheme for the following inputs: +// DedupNames(a, b) vs DedupNames(a), DedupNames(b) +// Empty vs non-empty PeerID, PublicAddress +// Empty vs non-empty Signer +func TestNewIdentityChallengeScheme(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + s1 := NewIdentityChallengeScheme() + s2 := NewIdentityChallengeScheme(NetIdentityDedupNames("")) + s3 := NewIdentityChallengeScheme(NetIdentityDedupNames("", "")) + s4 := NewIdentityChallengeScheme(NetIdentityDedupNames(""), NetIdentityDedupNames("")) + require.Equal(t, s1, s2) + require.Equal(t, s2, s3) + require.Equal(t, s3, s4) + require.Zero(t, *s1) + + s1 = NewIdentityChallengeScheme(NetIdentityDedupNames("a", "a")) + s2 = NewIdentityChallengeScheme(NetIdentityDedupNames("a"), NetIdentityDedupNames("a")) + require.Equal(t, s1.dedupNames, s2.dedupNames) + require.Len(t, s1.dedupNames, 1) + require.IsType(t, &identityChallengeLegacySigner{}, s1.identityKeys) + require.IsType(t, &identityChallengeLegacySigner{}, s2.identityKeys) + require.NotEqual(t, s1.identityKeys, s2.identityKeys) + + s1 = NewIdentityChallengeScheme(NetIdentityDedupNames("a", "b")) + s2 = NewIdentityChallengeScheme(NetIdentityDedupNames("a"), NetIdentityDedupNames("b")) + require.Equal(t, s1.dedupNames, s2.dedupNames) + require.Len(t, s1.dedupNames, 2) + require.IsType(t, &identityChallengeLegacySigner{}, s1.identityKeys) + require.IsType(t, &identityChallengeLegacySigner{}, s2.identityKeys) + require.NotEqual(t, s1.identityKeys, s2.identityKeys) + + s1 = NewIdentityChallengeScheme(NetIdentityDedupNames("", "a")) + s2 = NewIdentityChallengeScheme(NetIdentityDedupNames("a"), NetIdentityDedupNames("")) + s3 = NewIdentityChallengeScheme(NetIdentityDedupNames("a", "")) + s4 = NewIdentityChallengeScheme(NetIdentityDedupNames(""), NetIdentityDedupNames("a")) + require.Equal(t, s1.dedupNames, s2.dedupNames) + require.Equal(t, s2.dedupNames, s3.dedupNames) + require.Equal(t, s3.dedupNames, s4.dedupNames) + require.Len(t, s1.dedupNames, 1) + require.IsType(t, &identityChallengeLegacySigner{}, s1.identityKeys) + require.IsType(t, &identityChallengeLegacySigner{}, s2.identityKeys) + require.NotEqual(t, s1.identityKeys, s2.identityKeys) + + s1 = NewIdentityChallengeScheme(NetIdentityDedupNames("a"), NetIdentitySigner(&identityChallengeLegacySigner{})) + require.Len(t, s1.dedupNames, 1) + require.IsType(t, &identityChallengeLegacySigner{}, s1.identityKeys) + + var seed crypto.Seed + crypto.RandBytes(seed[:]) + signer := &identityChallengeLegacySigner{keys: crypto.GenerateSignatureSecrets(seed)} + s1 = NewIdentityChallengeScheme(NetIdentityDedupNames("a"), NetIdentitySigner(signer)) + require.Len(t, s1.dedupNames, 1) + require.IsType(t, &identityChallengeLegacySigner{}, s1.identityKeys) + require.Equal(t, signer, s1.identityKeys) + + s1 = NewIdentityChallengeScheme(NetIdentityDedupNames(""), NetIdentitySigner(signer)) + require.Empty(t, s1) + s1 = NewIdentityChallengeScheme(NetIdentitySigner(signer)) + require.Empty(t, s1) +} diff --git a/network/p2p/logger.go b/network/p2p/logger.go index 26c738e1e1..741755745b 100644 --- a/network/p2p/logger.go +++ b/network/p2p/logger.go @@ -19,6 +19,7 @@ package p2p import ( + "errors" "runtime" "strings" @@ -55,19 +56,37 @@ type loggingCore struct { zapcore.Core } +// ErrInvalidLogLevel is returned when an invalid log level is provided. +var ErrInvalidLogLevel = errors.New("invalid log level") + // EnableP2PLogging enables libp2p logging into the provided logger with the provided level. -func EnableP2PLogging(log logging.Logger, l logging.Level) { +func EnableP2PLogging(log logging.Logger, l logging.Level) error { core := loggingCore{ log: log, level: l, } + err := SetP2PLogLevel(l) + if err != nil { + return err + } + p2plogging.SetPrimaryCore(&core) + return nil +} + +// SetP2PLogLevel sets the log level for libp2p logging. +func SetP2PLogLevel(l logging.Level) error { + var seen bool for p2pLevel, logLevel := range levelsMap { if logLevel == l { p2plogging.SetAllLoggers(p2plogging.LogLevel(p2pLevel)) + seen = true break } } - p2plogging.SetPrimaryCore(&core) + if !seen { + return ErrInvalidLogLevel + } + return nil } func (c *loggingCore) Enabled(l zapcore.Level) bool { diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index f67f79f427..2c64b63eab 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -38,6 +38,7 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" + rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" "github.com/libp2p/go-libp2p/p2p/security/noise" "github.com/libp2p/go-libp2p/p2p/transport/tcp" @@ -116,9 +117,14 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. listenAddr = "" } - var disableMetrics = func(cfg *libp2p.Config) error { return nil } + var enableMetrics = func(cfg *libp2p.Config) error { cfg.DisableMetrics = false; return nil } metrics.DefaultRegistry().Register(&metrics.PrometheusDefaultMetrics) + rm, err := configureResourceManager(cfg) + if err != nil { + return nil, "", err + } + host, err := libp2p.New( libp2p.Identity(privKey), libp2p.UserAgent(ua), @@ -127,11 +133,29 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. libp2p.Peerstore(pstore), libp2p.NoListenAddrs, libp2p.Security(noise.ID, noise.New), - disableMetrics, + enableMetrics, + libp2p.ResourceManager(rm), ) return host, listenAddr, err } +func configureResourceManager(cfg config.Local) (network.ResourceManager, error) { + // see https://github.com/libp2p/go-libp2p/tree/master/p2p/host/resource-manager for more details + scalingLimits := rcmgr.DefaultLimits + libp2p.SetDefaultServiceLimits(&scalingLimits) + scaledDefaultLimits := scalingLimits.AutoScale() + + limitConfig := rcmgr.PartialLimitConfig{ + System: rcmgr.ResourceLimits{ + Conns: rcmgr.LimitVal(cfg.P2PIncomingConnectionsLimit), + }, + // Everything else is default. The exact values will come from `scaledDefaultLimits` above. + } + limiter := rcmgr.NewFixedLimiter(limitConfig.Build(scaledDefaultLimits)) + rm, err := rcmgr.NewResourceManager(limiter) + return rm, err +} + // MakeService creates a P2P service instance func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler, bootstrapPeers []*peer.AddrInfo) (*serviceImpl, error) { @@ -150,7 +174,6 @@ func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h ho return nil, err } return &serviceImpl{ - log: log, listenAddr: listenAddr, host: h, diff --git a/network/p2p/peerID_test.go b/network/p2p/peerID_test.go index 9d7729d593..beed18868c 100644 --- a/network/p2p/peerID_test.go +++ b/network/p2p/peerID_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -103,3 +104,18 @@ func TestGetPrivKeyUserGeneratedEphemeral(t *testing.T) { _, err = loadPrivateKeyFromFile(path.Join(tempdir, DefaultPrivKeyPath)) assert.True(t, os.IsNotExist(err)) } + +func TestPeerIDChallengeSigner(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + privKey, err := generatePrivKey() + require.NoError(t, err) + + data := make([]byte, 111) + crypto.RandBytes(data) + signer := PeerIDChallengeSigner{key: privKey} + pubKey := privKey.GetPublic() + pubKeyRaw, err := pubKey.Raw() + require.NoError(t, err) + require.Equal(t, crypto.PublicKey(pubKeyRaw), signer.PublicKey()) +} diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 4d6efdac83..9f5448c0ea 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -26,6 +26,7 @@ import ( "time" "github.com/algorand/go-algorand/config" + algocrypto "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/logging/telemetryspec" "github.com/algorand/go-algorand/network/limitcaller" @@ -81,6 +82,8 @@ type P2PNetwork struct { nodeInfo NodeInfo pstore *peerstore.PeerStore httpServer *p2p.HTTPServer + + identityTracker identityTracker } type bootstrapper struct { @@ -191,30 +194,8 @@ type p2pPeerStats struct { txReceived atomic.Uint64 } -// gossipSubPeer implements the DeadlineSettableConn, IPAddressable, and ErlClient interfaces. -type gossipSubPeer struct { - peerID peer.ID - net GossipNode - routingAddr [8]byte -} - -func (p gossipSubPeer) GetNetwork() GossipNode { return p.net } - -func (p gossipSubPeer) OnClose(f func()) { - net := p.GetNetwork().(*P2PNetwork) - net.wsPeersLock.Lock() - defer net.wsPeersLock.Unlock() - if wsp, ok := net.wsPeers[p.peerID]; ok { - wsp.OnClose(f) - } -} - -func (p gossipSubPeer) RoutingAddr() []byte { - return p.routingAddr[:] -} - // NewP2PNetwork returns an instance of GossipNode that uses the p2p.Service -func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, node NodeInfo) (*P2PNetwork, error) { +func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, node NodeInfo, identityOpts *identityOpts) (*P2PNetwork, error) { const readBufferLen = 2048 // create Peerstore and add phonebook addresses @@ -262,7 +243,17 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo broadcastQueueBulk: make(chan broadcastRequest, 100), } - p2p.EnableP2PLogging(log, logging.Level(cfg.BaseLoggerDebugLevel)) + if identityOpts != nil { + net.identityTracker = identityOpts.tracker + } + if net.identityTracker == nil { + net.identityTracker = noopIdentityTracker{} + } + + err = p2p.EnableP2PLogging(log, logging.Level(cfg.BaseLoggerDebugLevel)) + if err != nil { + return nil, err + } h, la, err := p2p.MakeHost(cfg, datadir, pstore) if err != nil { @@ -380,7 +371,16 @@ func (n *P2PNetwork) Stop() { n.wsPeersConnectivityCheckTicker = nil } n.innerStop() + + // This is a workaround for a race between PubSub.processLoop (triggered by context cancellation below) termination + // and this function returning that causes main goroutine to exit before + // PubSub.processLoop goroutine finishes logging its termination message + // to already closed logger. Not seen in wild, only in tests. + if n.log.GetLevel() >= logging.Warn { + _ = p2p.SetP2PLogLevel(logging.Warn) + } n.ctxCancel() + n.service.Close() n.bootstrapperStop() n.httpServer.Close() @@ -541,8 +541,6 @@ func (n *P2PNetwork) Disconnect(badpeer DisconnectablePeer) { n.wsPeersLock.Lock() defer n.wsPeersLock.Unlock() switch p := badpeer.(type) { - case gossipSubPeer: // Disconnect came from a message received via GossipSub - peerID, wsp = p.peerID, n.wsPeers[p.peerID] case *wsPeer: // Disconnect came from a message received via wsPeer peerID, wsp = n.wsPeersToIDs[p], p default: @@ -755,21 +753,32 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea ma := stream.Conn().RemoteMultiaddr() addr := ma.String() if addr == "" { - n.log.Warnf("Could not get address for peer %s", p2pPeer) + n.log.Warnf("Cannot get address for peer %s", p2pPeer) } - // create a wsPeer for this stream and added it to the peers map. + // create a wsPeer for this stream and added it to the peers map. addrInfo := &peer.AddrInfo{ID: p2pPeer, Addrs: []multiaddr.Multiaddr{ma}} maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) if err != nil { client = nil } + var netIdentPeerID algocrypto.PublicKey + if p2pPeerPubKey, err0 := p2pPeer.ExtractPublicKey(); err0 == nil { + if b, err0 := p2pPeerPubKey.Raw(); err0 == nil { + netIdentPeerID = algocrypto.PublicKey(b) + } else { + n.log.Warnf("Cannot get raw pubkey for peer %s", p2pPeer) + } + } else { + n.log.Warnf("Cannot get pubkey for peer %s", p2pPeer) + } peerCore := makePeerCore(ctx, n, n.log, n.handler.readBuffer, addr, client, addr) wsp := &wsPeer{ wsPeerCore: peerCore, conn: &wsPeerConnP2PImpl{stream: stream}, outgoing: !incoming, + identity: netIdentPeerID, } protos, err := n.pstore.GetProtocols(p2pPeer) if err != nil { @@ -777,6 +786,19 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea } wsp.TelemetryGUID, wsp.InstanceName = p2p.GetPeerTelemetryInfo(protos) + localAddr, has := n.Address() + if !has { + n.log.Warn("Could not get local address") + } + n.wsPeersLock.Lock() + ok := n.identityTracker.setIdentity(wsp) + n.wsPeersLock.Unlock() + if !ok { + networkPeerIdentityDisconnect.Inc(nil) + n.log.With("remote", addr).With("local", localAddr).Warn("peer deduplicated before adding because the identity is already known") + stream.Close() + } + wsp.init(n.config, outgoingMessagesBufferSize) n.wsPeersLock.Lock() n.wsPeers[p2pPeer] = wsp @@ -790,10 +812,6 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea event = "ConnectedIn" msg = "Accepted incoming connection from peer %s" } - localAddr, has := n.Address() - if !has { - n.log.Warn("Could not get local address") - } n.log.With("event", event).With("remote", addr).With("local", localAddr).Infof(msg, p2pPeer.String()) if n.log.GetLevel() >= logging.Debug { @@ -815,6 +833,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea func (n *P2PNetwork) peerRemoteClose(peer *wsPeer, reason disconnectReason) { remotePeerID := peer.conn.(*wsPeerConnP2PImpl).stream.Conn().RemotePeer() n.wsPeersLock.Lock() + n.identityTracker.removeIdentity(peer) delete(n.wsPeers, remotePeerID) delete(n.wsPeersToIDs, peer) n.wsPeersLock.Unlock() @@ -913,7 +932,9 @@ func (n *P2PNetwork) txTopicHandleLoop() { func (n *P2PNetwork) txTopicValidator(ctx context.Context, peerID peer.ID, msg *pubsub.Message) pubsub.ValidationResult { var routingAddr [8]byte n.wsPeersLock.Lock() - if wsp, ok := n.wsPeers[peerID]; ok { + var wsp *wsPeer + var ok bool + if wsp, ok = n.wsPeers[peerID]; ok { copy(routingAddr[:], wsp.RoutingAddr()) } else { // well, otherwise use last 8 bytes of peerID @@ -922,7 +943,8 @@ func (n *P2PNetwork) txTopicValidator(ctx context.Context, peerID peer.ID, msg * n.wsPeersLock.Unlock() inmsg := IncomingMessage{ - Sender: gossipSubPeer{peerID: msg.ReceivedFrom, net: n, routingAddr: routingAddr}, + // Sender: gossipSubPeer{peerID: msg.ReceivedFrom, net: n, routingAddr: routingAddr}, + Sender: wsp, Tag: protocol.TxnTag, Data: msg.Data, Net: n, diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index ff1f40a63c..0eac398431 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -39,7 +39,6 @@ import ( "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/go-algorand/util" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/crypto" @@ -69,7 +68,7 @@ func TestP2PSubmitTX(t *testing.T) { cfg.ForceFetchTransactions = true cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) netA.Start() defer netA.Stop() @@ -81,12 +80,12 @@ func TestP2PSubmitTX(t *testing.T) { multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} - netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) netB.Start() defer netB.Stop() - netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) netC.Start() defer netC.Stop() @@ -162,7 +161,7 @@ func TestP2PSubmitTXNoGossip(t *testing.T) { cfg.ForceFetchTransactions = true cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) netA.Start() defer netA.Stop() @@ -174,7 +173,7 @@ func TestP2PSubmitTXNoGossip(t *testing.T) { multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} - netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) netB.Start() defer netB.Stop() @@ -193,7 +192,7 @@ func TestP2PSubmitTXNoGossip(t *testing.T) { cfg.ForceFetchTransactions = false // Have to unset NetAddress to get IsGossipServer to return false cfg.NetAddress = "" - netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) netC.Start() defer netC.Stop() @@ -259,7 +258,7 @@ func TestP2PSubmitWS(t *testing.T) { cfg := config.GetDefaultLocal() cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) err = netA.Start() @@ -273,13 +272,13 @@ func TestP2PSubmitWS(t *testing.T) { multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} - netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) err = netB.Start() require.NoError(t, err) defer netB.Stop() - netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) err = netC.Start() require.NoError(t, err) @@ -393,7 +392,7 @@ func TestP2PNetworkAddress(t *testing.T) { cfg := config.GetDefaultLocal() log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) defer netA.Stop() require.NoError(t, err) addrInfo := netA.service.AddrInfo() @@ -605,7 +604,7 @@ func TestP2PNetworkDHTCapabilities(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, test.nis[0]) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, test.nis[0], nil) require.NoError(t, err) err = netA.Start() @@ -619,13 +618,13 @@ func TestP2PNetworkDHTCapabilities(t *testing.T) { multiAddrStr := addrsA[0].String() phoneBookAddresses := []string{multiAddrStr} - netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[1]) + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[1], nil) require.NoError(t, err) err = netB.Start() require.NoError(t, err) defer netB.Stop() - netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[2]) + netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[2], nil) require.NoError(t, err) err = netC.Start() require.NoError(t, err) @@ -753,7 +752,7 @@ func TestP2PHTTPHandler(t *testing.T) { cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) - netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) h := &p2phttpHandler{t, "hello", nil} @@ -822,7 +821,7 @@ func TestP2PRelay(t *testing.T) { cfg.NetAddress = "127.0.0.1:0" log := logging.TestingLog(t) log.Debugln("Starting netA") - netA, err := NewP2PNetwork(log.With("net", "netA"), cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netA, err := NewP2PNetwork(log.With("net", "netA"), cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) err = netA.Start() @@ -840,7 +839,7 @@ func TestP2PRelay(t *testing.T) { // Explicitly unset NetAddress for netB cfg.NetAddress = "" log.Debugf("Starting netB with phonebook addresses %v", phoneBookAddresses) - netB, err := NewP2PNetwork(log.With("net", "netB"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netB, err := NewP2PNetwork(log.With("net", "netB"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) err = netB.Start() require.NoError(t, err) @@ -907,7 +906,7 @@ func TestP2PRelay(t *testing.T) { // ensure all messages from netB and netC are received by netA cfg.NetAddress = "127.0.0.1:0" log.Debugf("Starting netC with phonebook addresses %v", phoneBookAddresses) - netC, err := NewP2PNetwork(log.With("net", "netC"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}) + netC, err := NewP2PNetwork(log.With("net", "netC"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) require.NoError(t, err) require.True(t, netC.relayMessages) err = netC.Start() @@ -1155,22 +1154,3 @@ func TestMergeP2PAddrInfoResolvedAddresses(t *testing.T) { }) } } - -// TestP2PGossipSubPeerCasts checks that gossipSubPeer implements the ErlClient and IPAddressable interfaces -// needed by TxHandler -func TestP2PGossipSubPeerCasts(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - var g interface{} = gossipSubPeer{} - _, ok := g.(util.ErlClient) - require.True(t, ok) - - _, ok = g.(IPAddressable) - require.True(t, ok) - - // check that gossipSubPeer is hashable as ERL wants - var m map[util.ErlClient]struct{} - require.Equal(t, m[gossipSubPeer{}], struct{}{}) - require.Equal(t, m[g.(util.ErlClient)], struct{}{}) -} diff --git a/network/requestLogger_test.go b/network/requestLogger_test.go index 0de6a41c73..c6bde8956e 100644 --- a/network/requestLogger_test.go +++ b/network/requestLogger_test.go @@ -50,12 +50,13 @@ func TestRequestLogger(t *testing.T) { dl := eventsDetailsLogger{Logger: log, eventReceived: make(chan interface{}, 1), eventIdentifier: telemetryspec.HTTPRequestEvent} log.SetLevel(logging.Level(defaultConfig.BaseLoggerDebugLevel)) netA := &WebsocketNetwork{ - log: dl, - config: defaultConfig, - phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), - GenesisID: "go-test-network-genesis", - NetworkID: config.Devtestnet, - peerStater: peerConnectionStater{log: log}, + log: dl, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: "go-test-network-genesis", + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, + identityTracker: noopIdentityTracker{}, } netA.config.EnableRequestLogger = true netA.setup() diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go index d814507c78..46f003e0f8 100644 --- a/network/requestTracker_test.go +++ b/network/requestTracker_test.go @@ -87,12 +87,13 @@ func TestRateLimiting(t *testing.T) { // This test is conducted locally, so we want to treat all hosts the same for counting incoming requests. testConfig.DisableLocalhostConnectionRateLimit = false wn := &WebsocketNetwork{ - log: log, - config: testConfig, - phonebook: phonebook.MakePhonebook(1, 1), - GenesisID: "go-test-network-genesis", - NetworkID: config.Devtestnet, - peerStater: peerConnectionStater{log: log}, + log: log, + config: testConfig, + phonebook: phonebook.MakePhonebook(1, 1), + GenesisID: "go-test-network-genesis", + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, + identityTracker: noopIdentityTracker{}, } // increase the IncomingConnectionsLimit/MaxConnectionsPerIP limits, since we don't want to test these. diff --git a/network/wsNetwork.go b/network/wsNetwork.go index f222d2ff27..1c0f3e8676 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -214,9 +214,6 @@ type WebsocketNetwork struct { NetworkID protocol.NetworkID RandomID string - peerID p2p.PeerID - peerIDSigner identityChallengeSigner - ready atomic.Int32 readyChan chan struct{} @@ -631,8 +628,6 @@ func (wn *WebsocketNetwork) setup() { wn.outgoingMessagesBufferSize = outgoingMessagesBufferSize wn.wsMaxHeaderBytes = wsMaxHeaderBytes - wn.identityTracker = NewIdentityTracker() - wn.broadcaster = msgBroadcaster{ ctx: wn.ctx, log: wn.log, @@ -699,7 +694,7 @@ func (wn *WebsocketNetwork) Start() error { wn.messagesOfInterestEnc = MarshallMessageOfInterestMap(wn.messagesOfInterest) } - if wn.config.IsGossipServer() { + if wn.config.IsGossipServer() || wn.config.ForceRelayMessages { listener, err := net.Listen("tcp", wn.config.NetAddress) if err != nil { wn.log.Errorf("network could not listen %v: %s", wn.config.NetAddress, err) @@ -736,16 +731,11 @@ func (wn *WebsocketNetwork) Start() error { } } // if the network has a public address or a libp2p peer ID, use that as the name for connection deduplication - if wn.config.PublicAddress != "" || (wn.peerID != "" && wn.peerIDSigner != nil) { + if wn.config.PublicAddress != "" || wn.identityScheme != nil { wn.RegisterHandlers(identityHandlers) } if wn.identityScheme == nil { - if wn.peerID != "" && wn.peerIDSigner != nil { - wn.identityScheme = NewIdentityChallengeSchemeWithSigner(string(wn.peerID), wn.peerIDSigner) - } - if wn.config.PublicAddress != "" { - wn.identityScheme = NewIdentityChallengeScheme(wn.config.PublicAddress) - } + wn.identityScheme = NewIdentityChallengeScheme(NetIdentityDedupNames(wn.config.PublicAddress)) } wn.meshUpdateRequests <- meshRequest{false, nil} @@ -2115,7 +2105,8 @@ func (wn *WebsocketNetwork) tryConnect(netAddr, gossipAddr string) { var idChallenge identityChallengeValue if wn.identityScheme != nil { - idChallenge = wn.identityScheme.AttachChallenge(requestHeader, netAddr) + theirAddr := strings.ToLower(netAddr) + idChallenge = wn.identityScheme.AttachChallenge(requestHeader, theirAddr) } // for backward compatibility, include the ProtocolVersion header as well. @@ -2305,7 +2296,7 @@ func (wn *WebsocketNetwork) SetPeerData(peer Peer, key string, value interface{} } // NewWebsocketNetwork constructor for websockets based gossip network -func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo, peerID p2p.PeerID, idSigner identityChallengeSigner) (wn *WebsocketNetwork, err error) { +func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo, identityOpts *identityOpts) (wn *WebsocketNetwork, err error) { pb := phonebook.MakePhonebook(config.ConnectionsRateLimitingCount, time.Duration(config.ConnectionsRateLimitingWindowSeconds)*time.Second) @@ -2324,8 +2315,6 @@ func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddre GenesisID: genesisID, NetworkID: networkID, nodeInfo: nodeInfo, - peerID: peerID, - peerIDSigner: idSigner, resolveSRVRecords: tools_network.ReadFromSRV, peerStater: peerConnectionStater{ log: log, @@ -2334,13 +2323,22 @@ func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddre }, } + // initialize net identity tracker either from the provided options or with a new one + if identityOpts != nil { + wn.identityScheme = identityOpts.scheme + wn.identityTracker = identityOpts.tracker + } + if wn.identityTracker == nil { + wn.identityTracker = NewIdentityTracker() + } + wn.setup() return wn, nil } // NewWebsocketGossipNode constructs a websocket network node and returns it as a GossipNode interface implementation func NewWebsocketGossipNode(log logging.Logger, config config.Local, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID) (gn GossipNode, err error) { - return NewWebsocketNetwork(log, config, phonebookAddresses, genesisID, networkID, nil, "", nil) + return NewWebsocketNetwork(log, config, phonebookAddresses, genesisID, networkID, nil, nil) } // SetPrioScheme specifies the network priority scheme for a network node diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 038a9d6e2d..6af3a697fc 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -128,12 +128,13 @@ func makeTestWebsocketNodeWithConfig(t testing.TB, conf config.Local, opts ...te log := logging.TestingLog(t) log.SetLevel(logging.Warn) wn := &WebsocketNetwork{ - log: log, - config: conf, - phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, - peerStater: peerConnectionStater{log: log}, + log: log, + config: conf, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, + identityTracker: NewIdentityTracker(), } // apply options to newly-created WebsocketNetwork, if provided for _, opt := range opts { @@ -1055,12 +1056,13 @@ func makeTestFilterWebsocketNode(t *testing.T, nodename string) *WebsocketNetwor dc.OutgoingMessageFilterBucketCount = 3 dc.OutgoingMessageFilterBucketSize = 128 wn := &WebsocketNetwork{ - log: logging.TestingLog(t).With("node", nodename), - config: dc, - phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, - peerStater: peerConnectionStater{log: logging.TestingLog(t).With("node", nodename)}, + log: logging.TestingLog(t).With("node", nodename), + config: dc, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: logging.TestingLog(t).With("node", nodename)}, + identityTracker: noopIdentityTracker{}, } require.True(t, wn.config.EnableIncomingMessageFilter) wn.setup() @@ -1696,7 +1698,7 @@ type mockIdentityScheme struct { } func newMockIdentityScheme(t *testing.T) *mockIdentityScheme { - return &mockIdentityScheme{t: t, realScheme: NewIdentityChallengeScheme("any")} + return &mockIdentityScheme{t: t, realScheme: NewIdentityChallengeScheme(NetIdentityDedupNames("any"))} } func (i mockIdentityScheme) AttachChallenge(attach http.Header, addr string) identityChallengeValue { if i.attachChallenge != nil { @@ -1768,7 +1770,7 @@ func TestPeeringWithBadIdentityChallenge(t *testing.T) { { name: "incorrect address", attachChallenge: func(attach http.Header, addr string) identityChallengeValue { - s := NewIdentityChallengeScheme("does not matter") // make a scheme to use its keys + s := NewIdentityChallengeScheme(NetIdentityDedupNames("does not matter")) // make a scheme to use its keys c := identityChallenge{ Key: s.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), @@ -1786,7 +1788,7 @@ func TestPeeringWithBadIdentityChallenge(t *testing.T) { { name: "bad signature", attachChallenge: func(attach http.Header, addr string) identityChallengeValue { - s := NewIdentityChallengeScheme("does not matter") // make a scheme to use its keys + s := NewIdentityChallengeScheme(NetIdentityDedupNames("does not matter")) // make a scheme to use its keys c := identityChallenge{ Key: s.identityKeys.PublicKey(), Challenge: newIdentityChallengeValue(), @@ -1901,7 +1903,7 @@ func TestPeeringWithBadIdentityChallengeResponse(t *testing.T) { { name: "incorrect original challenge", verifyAndAttachResponse: func(attach http.Header, h http.Header) (identityChallengeValue, crypto.PublicKey, error) { - s := NewIdentityChallengeScheme("does not matter") // make a scheme to use its keys + s := NewIdentityChallengeScheme(NetIdentityDedupNames("does not matter")) // make a scheme to use its keys // decode the header to an identityChallenge msg, _ := base64.StdEncoding.DecodeString(h.Get(IdentityChallengeHeader)) idChal := identityChallenge{} @@ -1924,7 +1926,7 @@ func TestPeeringWithBadIdentityChallengeResponse(t *testing.T) { { name: "bad signature", verifyAndAttachResponse: func(attach http.Header, h http.Header) (identityChallengeValue, crypto.PublicKey, error) { - s := NewIdentityChallengeScheme("does not matter") // make a scheme to use its keys + s := NewIdentityChallengeScheme(NetIdentityDedupNames("does not matter")) // make a scheme to use its keys // decode the header to an identityChallenge msg, _ := base64.StdEncoding.DecodeString(h.Get(IdentityChallengeHeader)) idChal := identityChallenge{} @@ -2056,7 +2058,7 @@ func TestPeeringWithBadIdentityVerification(t *testing.T) { resp := identityChallengeResponseSigned{} err = protocol.Decode(msg, &resp) require.NoError(t, err) - s := NewIdentityChallengeScheme("does not matter") // make a throwaway key + s := NewIdentityChallengeScheme(NetIdentityDedupNames("does not matter")) // make a throwaway key ver := identityVerificationMessageSigned{ // fill in correct ResponseChallenge field Msg: identityVerificationMessage{ResponseChallenge: resp.Msg.ResponseChallenge}, @@ -2074,7 +2076,7 @@ func TestPeeringWithBadIdentityVerification(t *testing.T) { // when the verification signature doesn't match the peer's expectation (the previously exchanged identity), peer is disconnected name: "bad signature", verifyResponse: func(t *testing.T, h http.Header, c identityChallengeValue) (crypto.PublicKey, []byte, error) { - s := NewIdentityChallengeScheme("does not matter") // make a throwaway key + s := NewIdentityChallengeScheme(NetIdentityDedupNames("does not matter")) // make a throwaway key ver := identityVerificationMessageSigned{ // fill in wrong ResponseChallenge field Msg: identityVerificationMessage{ResponseChallenge: newIdentityChallengeValue()}, @@ -2566,12 +2568,13 @@ func TestSlowPeerDisconnection(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Info) wn := &WebsocketNetwork{ - log: log, - config: defaultConfig, - phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, - peerStater: peerConnectionStater{log: log}, + log: log, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, + identityTracker: noopIdentityTracker{}, } wn.setup() wn.broadcaster.slowWritingPeerMonitorInterval = time.Millisecond * 50 @@ -2642,12 +2645,13 @@ func TestForceMessageRelaying(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Level(defaultConfig.BaseLoggerDebugLevel)) wn := &WebsocketNetwork{ - log: log, - config: defaultConfig, - phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, - peerStater: peerConnectionStater{log: log}, + log: log, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, + identityTracker: noopIdentityTracker{}, } wn.setup() wn.eventualReadyDelay = time.Second @@ -2737,12 +2741,13 @@ func TestCheckProtocolVersionMatch(t *testing.T) { log := logging.TestingLog(t) log.SetLevel(logging.Level(defaultConfig.BaseLoggerDebugLevel)) wn := &WebsocketNetwork{ - log: log, - config: defaultConfig, - phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), - GenesisID: genesisID, - NetworkID: config.Devtestnet, - peerStater: peerConnectionStater{log: log}, + log: log, + config: defaultConfig, + phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond), + GenesisID: genesisID, + NetworkID: config.Devtestnet, + peerStater: peerConnectionStater{log: log}, + identityTracker: noopIdentityTracker{}, } wn.setup() wn.supportedProtocolVersions = []string{"2", "1"} @@ -4560,7 +4565,6 @@ func TestWsNetworkPhonebookMix(t *testing.T) { "test", "net", nil, - "", nil, ) require.NoError(t, err) diff --git a/node/follower_node.go b/node/follower_node.go index 117cc56e86..7d8fc64388 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -94,7 +94,7 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo node.config = cfg // tie network, block fetcher, and agreement services together - p2pNode, err := network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, nil, "", nil) + p2pNode, err := network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, nil, nil) if err != nil { log.Errorf("could not create websocket node: %v", err) return nil, err diff --git a/node/node.go b/node/node.go index 5f1baa56be..b6118aadc0 100644 --- a/node/node.go +++ b/node/node.go @@ -206,14 +206,14 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd return nil, err } } else if cfg.EnableP2P { - p2pNode, err = network.NewP2PNetwork(node.log, node.config, rootDir, phonebookAddresses, genesis.ID(), genesis.Network, node) + p2pNode, err = network.NewP2PNetwork(node.log, node.config, rootDir, phonebookAddresses, genesis.ID(), genesis.Network, node, nil) 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, "", nil) + wsNode, err = network.NewWebsocketNetwork(node.log, node.config, phonebookAddresses, genesis.ID(), genesis.Network, node, nil) if err != nil { log.Errorf("could not create websocket node: %v", err) return nil, err diff --git a/node/node_test.go b/node/node_test.go index 3ea6d4a33d..e17e3e8d3f 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -875,6 +875,7 @@ func TestNodeHybridTopology(t *testing.T) { cfg.NetAddress = ni.wsNetAddr() cfg.EnableP2PHybridMode = true + cfg.PublicAddress = ni.wsNetAddr() cfg.EnableDHTProviders = true cfg.P2PPersistPeerID = true privKey, err := p2p.GetPrivKey(cfg, ni.rootDir) diff --git a/test/testdata/configs/config-v34.json b/test/testdata/configs/config-v34.json index 7f16155303..3a9714bbfb 100644 --- a/test/testdata/configs/config-v34.json +++ b/test/testdata/configs/config-v34.json @@ -98,6 +98,7 @@ "OptimizeAccountsDatabaseOnStartup": false, "OutgoingMessageFilterBucketCount": 3, "OutgoingMessageFilterBucketSize": 128, + "P2PIncomingConnectionsLimit": 1200, "P2PNetAddress": "", "P2PPersistPeerID": false, "P2PPrivateKeyLocation": "", diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile index f4ec4b3c1f..7222fd3882 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/Makefile @@ -3,15 +3,17 @@ PARAMS=-w 20 -R 8 -N 20 -n 20 --npn-algod-nodes 10 --node-template node.json --r .PHONY: clean all +HYBRID ?= no + all: net.json genesis.json topology.json -node.json nonPartNode.json relay.json: - python3 copy-node-configs.py +node.json nonPartNode.json relay.json: copy-node-configs.py + python3 copy-node-configs.py --hybrid=${HYBRID} -net.json: node.json nonPartNode.json relay.json ${GOPATH}/bin/netgoal Makefile +net.json: node.json nonPartNode.json relay.json Makefile netgoal generate -t net -r /tmp/wat -o net.json ${PARAMS} -genesis.json: ${GOPATH}/bin/netgoal Makefile +genesis.json: Makefile netgoal generate -t genesis -r /tmp/wat -o genesis.l.json ${PARAMS} jq '.LastPartKeyRound=5000|.NetworkName="s1s-p2p"|.ConsensusProtocol="future"' < genesis.l.json > genesis.json rm genesis.l.json diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md index 1cad95bc2d..04e8b986c7 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/README.md @@ -7,10 +7,17 @@ This is a copy of scenario1s with the following changes in nodes configuration: ## Build ```sh -export GOPATH=~/go make ``` +If want to configure a hybrid net, set the `HYBRID` mode parameter to: + - `p2p` meaning all nodes are p2pnet and 50% of them are hybrid + - `ws` meaning all nodes are wsnet and 50% of them are hybrid + +```sh +make -D HYBRID=p2p +``` + ## Run Run as usual cluster test scenario with algonet. diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py index 6ffbc01d8d..12da86f348 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py @@ -5,14 +5,120 @@ 3. Set DNSSecurityFlags: 0 to all configs """ +import argparse +import copy import json import os CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) SCENARIO1S_DIR = os.path.join(CURRENT_DIR, "..", "scenario1s") +def make_p2p_net(*args): + """convert config to a pure p2p network""" + for config in args: + override_json = json.loads(config.get("ConfigJSONOverride", "{}")) + override_json["EnableP2P"] = True + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + config["ConfigJSONOverride"] = json.dumps(override_json) + + net_address = config.get("NetAddress") + if net_address: + config["P2PBootstrap"] = True + altconfigs = config.get("AltConfigs", []) + if altconfigs: + for i, altconfig in enumerate(altconfigs): + override_json = json.loads(altconfig.get("ConfigJSONOverride", "{}")) + override_json["EnableP2P"] = True + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + altconfigs[i]["ConfigJSONOverride"] = json.dumps(override_json) + config["AltConfigs"] = altconfigs + + +def make_hybrid_p2p_net(*args): + """convert config to a hybrid p2p network: + - half of relays become hybrid and receive public address + - half of non-relay nodes become hybrid + - AltConfigs are used for hybrid nodes with FractionApply=0.5 + - Only one AltConfigs is supported and its FractionApply is forced to 0.5 + """ + for config in args: + override_json = json.loads(config.get("ConfigJSONOverride", "{}")) + override_json["EnableP2P"] = True + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + config["ConfigJSONOverride"] = json.dumps(override_json) + + net_address = config.get("NetAddress") + if net_address: + # in p2p-only mode all relays are P2PBootstrap-able + config["P2PBootstrap"] = True + + altconfigs = config.get("AltConfigs") + altconfig = None + if altconfigs: + altconfig = altconfigs[0] + else: + altconfig = copy.deepcopy(config) + + override_json = json.loads(altconfig.get("ConfigJSONOverride", "{}")) + override_json["EnableP2PHybridMode"] = True + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + altconfig["ConfigJSONOverride"] = json.dumps(override_json) + if net_address: # relay, set public address + altconfig["P2PBootstrap"] = True + altconfig["P2PNetAddress"] = "{{NetworkPort2}}" + altconfig["PublicAddress"] = True + altconfig['FractionApply'] = 0.5 + + altconfigs = [altconfig] + config["AltConfigs"] = altconfigs + + +def make_hybrid_ws_net(*args): + """convert config to a hybrid ws network: + - half of relays become hybrid and receive public address + - half of non-relay nodes become hybrid + - AltConfigs are used for hybrid nodes with FractionApply=0.5 + - Only one AltConfigs is supported and its FractionApply is forced to 0.5 + """ + for config in args: + override_json = json.loads(config.get("ConfigJSONOverride", "{}")) + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + config["ConfigJSONOverride"] = json.dumps(override_json) + + net_address = config.get("NetAddress") + altconfigs = config.get("AltConfigs") + altconfig = None + if altconfigs: + altconfig = altconfigs[0] + else: + altconfig = copy.deepcopy(config) + + override_json = json.loads(altconfig.get("ConfigJSONOverride", "{}")) + override_json["EnableP2PHybridMode"] = True + override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC + altconfig["ConfigJSONOverride"] = json.dumps(override_json) + if net_address: # relay, set public address + altconfig["P2PBootstrap"] = True + altconfig["P2PNetAddress"] = "{{NetworkPort2}}" + altconfig["PublicAddress"] = True + altconfig['FractionApply'] = 0.5 + + altconfigs = [altconfig] + config["AltConfigs"] = altconfigs + + def main(): """main""" + ap = argparse.ArgumentParser() + ap.add_argument('--hybrid', type=str, help='Hybrid mode: p2p, ws') + args = ap.parse_args() + + hybrid_mode = args.hybrid + if hybrid_mode not in ("p2p", "ws"): + hybrid_mode = None + + print('Hybrid mode:', hybrid_mode) + with open(os.path.join(SCENARIO1S_DIR, "node.json"), "r") as f: node = json.load(f) with open(os.path.join(SCENARIO1S_DIR, "relay.json"), "r") as f: @@ -20,27 +126,15 @@ def main(): with open(os.path.join(SCENARIO1S_DIR, "nonPartNode.json"), "r") as f: non_part_node = json.load(f) - # make all relays P2PBootstrap'able - relay["P2PBootstrap"] = True - - # enable P2P for all configs - for config in (node, relay, non_part_node): - override = config.get("ConfigJSONOverride") - if override: - override_json = json.loads(override) - override_json["EnableP2P"] = True - override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC - config["ConfigJSONOverride"] = json.dumps(override_json) - altconfigs = config.get("AltConfigs", []) - if altconfigs: - for i, altconfig in enumerate(altconfigs): - override = altconfig.get("ConfigJSONOverride") - if override: - override_json = json.loads(override) - override_json["EnableP2P"] = True - override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC - altconfigs[i]["ConfigJSONOverride"] = json.dumps(override_json) - config["AltConfigs"] = altconfigs + if hybrid_mode == 'p2p': + print('making hybrid p2p network...') + make_hybrid_p2p_net(node, relay, non_part_node) + elif hybrid_mode == 'ws': + print('making hybrid ws network...') + make_hybrid_ws_net(node, relay, non_part_node) + else: + print('making pure p2p network...') + make_p2p_net(node, relay, non_part_node) with open("node.json", "w") as f: json.dump(node, f, indent=4) From 2d385ffeab6438ca2765fb8e56a8ee1abb54ebb7 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:06:37 -0400 Subject: [PATCH 178/218] build: quiet duplicate libraries and ATOMIC_VAR_INT messages (#6072) --- Makefile | 1 + go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 7ad7f219f8..bf544242c0 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,7 @@ endif # M1 Mac--homebrew install location in /opt/homebrew ifeq ($(OS_TYPE), darwin) ifeq ($(ARCH), arm64) +EXTLDFLAGS := -Wl,-no_warn_duplicate_libraries export CPATH=/opt/homebrew/include export LIBRARY_PATH=/opt/homebrew/lib endif diff --git a/go.mod b/go.mod index e4d8c5b8d5..e078ec73fb 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/ipfs/go-log v1.0.5 github.com/ipfs/go-log/v2 v2.5.1 github.com/jmoiron/sqlx v1.2.0 - github.com/karalabe/usb v0.0.2 + github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c github.com/labstack/echo/v4 v4.9.1 github.com/libp2p/go-libp2p v0.33.2 github.com/libp2p/go-libp2p-kad-dht v0.24.3 diff --git a/go.sum b/go.sum index 1a52e04711..66924d7486 100644 --- a/go.sum +++ b/go.sum @@ -313,8 +313,8 @@ github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPci github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4= -github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c h1:AqsttAyEyIEsNz5WLRwuRwjiT5CMDUfLk6cFJDVPebs= +github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= From 75bb6a90320c3eb924c482378663cb8dd451d123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Pier=C5=9Bcionek?= Date: Fri, 19 Jul 2024 20:08:07 +0200 Subject: [PATCH 179/218] catchup: skip logging err for catchup cancellation (#6053) --- catchup/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catchup/service.go b/catchup/service.go index b00a787ee3..a89f6bcecc 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -351,7 +351,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo // for no reason. select { case <-ctx.Done(): - s.log.Infof("fetchAndWrite(%d): Aborted while waiting for lookback block to ledger after failing once : %v", r, err) + s.log.Infof("fetchAndWrite(%v): Aborted while waiting for lookback block to ledger", r) return false case <-lookbackComplete: } From 47fd1c93f5c05c53c39ad60832f6f6f9748dae5d Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:03:38 -0400 Subject: [PATCH 180/218] build: make -no_warn_duplicate_libraries conditional on Xcode >= 15 (#6074) --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index bf544242c0..5204f24345 100644 --- a/Makefile +++ b/Makefile @@ -49,10 +49,14 @@ else export GOTESTCOMMAND=gotestsum --format pkgname --jsonfile testresults.json -- endif -# M1 Mac--homebrew install location in /opt/homebrew ifeq ($(OS_TYPE), darwin) -ifeq ($(ARCH), arm64) +# For Xcode >= 15, set -no_warn_duplicate_libraries linker option +CLANG_MAJOR_VERSION := $(shell clang --version | grep '^Apple clang version ' | awk '{print $$4}' | cut -d. -f1) +ifeq ($(shell [ $(CLANG_MAJOR_VERSION) -ge 15 ] && echo true), true) EXTLDFLAGS := -Wl,-no_warn_duplicate_libraries +endif +# M1 Mac--homebrew install location in /opt/homebrew +ifeq ($(ARCH), arm64) export CPATH=/opt/homebrew/include export LIBRARY_PATH=/opt/homebrew/lib endif From f6fa5906384f62f5cc0b47a5f30a55311a3c7dff Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 25 Jul 2024 13:53:44 -0400 Subject: [PATCH 181/218] CI: update nightly builds to use universal mac update (#6071) --- .circleci/config.yml | 2 +- Makefile | 11 ++++++--- cmd/updater/update.sh | 4 ---- package-upload.yaml | 23 ------------------ scripts/release/mule/Makefile.mule | 17 +++++++++---- scripts/release/mule/README.md | 19 --------------- scripts/travis/deploy_packages.sh | 13 ++++++++-- test/muleCI/mule.yaml | 38 ++++++++---------------------- util/s3/s3Helper.go | 12 +++++++++- 9 files changed, 53 insertions(+), 86 deletions(-) delete mode 100644 package-upload.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml index 4057049dac..13d4ea996f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -715,7 +715,7 @@ commands: command: | if [ "${CIRCLE_BRANCH}" = "rel/nightly" ] then - export NO_BUILD="true" + export NIGHTLY_BUILD="true" fi export PATH=$(echo "$PATH" | sed -e "s|:${HOME}/\.go_workspace/bin||g" | sed -e 's|:/usr/local/go/bin||g') export GOPATH="<< parameters.build_dir >>/go" diff --git a/Makefile b/Makefile index 5204f24345..52c505b301 100644 --- a/Makefile +++ b/Makefile @@ -179,11 +179,16 @@ ifeq ($(OS_TYPE),darwin) mkdir -p $(GOPATH1)/bin-darwin-arm64 CROSS_COMPILE_ARCH=arm64 GOBIN=$(GOPATH1)/bin-darwin-arm64 MACOSX_DEPLOYMENT_TARGET=12.0 EXTRA_CONFIGURE_FLAGS='CFLAGS="-arch arm64 -mmacos-version-min=12.0" --host=aarch64-apple-darwin' $(MAKE) + # same for buildsrc-special + cd tools/block-generator && \ + CROSS_COMPILE_ARCH=amd64 GOBIN=$(GOPATH1)/bin-darwin-amd64 MACOSX_DEPLOYMENT_TARGET=12.0 EXTRA_CONFIGURE_FLAGS='CFLAGS="-arch x86_64 -mmacos-version-min=12.0" --host=x86_64-apple-darwin' $(MAKE) + CROSS_COMPILE_ARCH=arm64 GOBIN=$(GOPATH1)/bin-darwin-arm64 MACOSX_DEPLOYMENT_TARGET=12.0 EXTRA_CONFIGURE_FLAGS='CFLAGS="-arch arm64 -mmacos-version-min=12.0" --host=aarch64-apple-darwin' $(MAKE) + # lipo together - mkdir -p $(GOPATH1)/bin-darwin-universal + mkdir -p $(GOPATH1)/bin for binary in $$(ls $(GOPATH1)/bin-darwin-arm64); do \ if [ -f $(GOPATH1)/bin-darwin-amd64/$$binary ]; then \ - lipo -create -output $(GOPATH1)/bin-darwin-universal/$$binary \ + lipo -create -output $(GOPATH1)/bin/$$binary \ $(GOPATH1)/bin-darwin-arm64/$$binary \ $(GOPATH1)/bin-darwin-amd64/$$binary; \ else \ @@ -191,7 +196,7 @@ ifeq ($(OS_TYPE),darwin) fi \ done else - $(error OS_TYPE must be darwin for universal builds) + echo "OS_TYPE must be darwin for universal builds, skipping" endif deps: diff --git a/cmd/updater/update.sh b/cmd/updater/update.sh index 6c20d55c2d..c7f049eec9 100755 --- a/cmd/updater/update.sh +++ b/cmd/updater/update.sh @@ -185,10 +185,6 @@ function get_updater_url() { UNAME=$(uname -m) if [[ "${UNAME}" = "x86_64" ]]; then ARCH="amd64" - elif [[ "${UNAME}" = "armv6l" ]]; then - ARCH="arm" - elif [[ "${UNAME}" = "armv7l" ]]; then - ARCH="arm" elif [[ "${UNAME}" = "aarch64" ]]; then ARCH="arm64" else diff --git a/package-upload.yaml b/package-upload.yaml deleted file mode 100644 index 9d13c458ef..0000000000 --- a/package-upload.yaml +++ /dev/null @@ -1,23 +0,0 @@ -tasks: - - task: s3.BucketCopy - name: amd64 - src: $HOME/projects/go-algorand/tmp/node_pkgs/linux/amd64 - dest: s3://$STAGING/$CHANNEL/$VERSION/ - - - task: s3.BucketCopy - name: arm - src: $HOME/projects/go-algorand/tmp/node_pkgs/linux/arm - dest: s3://$STAGING/$CHANNEL/$VERSION/ - - - task: s3.BucketCopy - name: arm64 - src: $HOME/projects/go-algorand/tmp/node_pkgs/linux/arm64 - dest: s3://$STAGING/$CHANNEL/$VERSION/ - -jobs: - package-upload: - tasks: - - s3.BucketCopy.amd64 - - s3.BucketCopy.arm - - s3.BucketCopy.arm64 - diff --git a/scripts/release/mule/Makefile.mule b/scripts/release/mule/Makefile.mule index 0c73cccf89..06fe5d09b7 100644 --- a/scripts/release/mule/Makefile.mule +++ b/scripts/release/mule/Makefile.mule @@ -1,15 +1,13 @@ # This file is imported into go-algorand/Makefile. PKG_DIR = $(SRCPATH)/tmp/node_pkgs/$(OS_TYPE)/$(ARCH) +PKG_DIR_UNIVERSAL = $(SRCPATH)/tmp/node_pkgs/$(OS_TYPE)/universal -.PHONY: ci-clean ci-setup ci-build +.PHONY: ci-clean ci-build ci-clean: clean rm -rf tmp -ci-setup: - mkdir -p $(PKG_DIR) - ci-test: ifeq ($(ARCH), amd64) RACE=-race @@ -28,7 +26,16 @@ ci-integration: SRCROOT=$(SRCPATH) \ test/scripts/e2e.sh -c $(CHANNEL) -n -ci-build: ci-clean build ci-setup +ci-build-universal: ci-clean universal + echo $(PKG_DIR_UNIVERSAL) + mkdir -p $(PKG_DIR_UNIVERSAL) + CHANNEL=$(CHANNEL) PKG_ROOT=$(PKG_DIR_UNIVERSAL) NO_BUILD=True VARIATIONS=$(OS_TYPE)-universal \ + scripts/build_packages.sh $(OS_TYPE)/universal && \ + mkdir -p $(PKG_DIR_UNIVERSAL)/data && \ + cp installer/genesis/devnet/genesis.json $(PKG_DIR_UNIVERSAL)/data + +ci-build: ci-clean + mkdir -p $(PKG_DIR) CHANNEL=$(CHANNEL) PKG_ROOT=$(PKG_DIR) NO_BUILD=True VARIATIONS=$(OS_TYPE)-$(ARCH) \ scripts/build_packages.sh $(OS_TYPE)/$(ARCH) && \ mkdir -p $(PKG_DIR)/data && \ diff --git a/scripts/release/mule/README.md b/scripts/release/mule/README.md index 8439767725..030728ef70 100644 --- a/scripts/release/mule/README.md +++ b/scripts/release/mule/README.md @@ -25,7 +25,6 @@ In addition, make sure that the following AWS credentials are set in environment # Build Stages - [package](#package) -- [upload](#upload) - [test](#test) - [sign](#sign) - [deploy](#deploy) @@ -49,20 +48,6 @@ In addition, make sure that the following AWS credentials are set in environment - package-docker + packages docker image -## upload - -- see `./go-algorand/package-upload.yaml` - -- customizable environment variables: - - + `CHANNEL` - + `STAGING` - + `VERSION` - -#### `mule` jobs - - - package-upload - ## test - see `./go-algorand/package-test.yaml` @@ -180,10 +165,6 @@ Let's look at some examples. mule -f package.yaml package -### Uploading - - STAGING=the-staging-area CHANNEL=beta VERSION=latest mule -f package-upload.yaml package-upload - ### Testing 1. As part of the test suite, the `verify_package_string.sh` test needs the `BRANCH` as well as the `SHA`: diff --git a/scripts/travis/deploy_packages.sh b/scripts/travis/deploy_packages.sh index e7e517394c..5879835b01 100755 --- a/scripts/travis/deploy_packages.sh +++ b/scripts/travis/deploy_packages.sh @@ -11,7 +11,7 @@ set -e SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" - +OSARCH=$("${SCRIPTPATH}/../osarchtype.sh") # Get the go build version. GOLANG_VERSION=$("${SCRIPTPATH}/../get_golang_version.sh") @@ -24,6 +24,15 @@ then exit 1 fi +if [ "${NIGHTLY_BUILD}" == "true" ]; then + # we want to rebuild universal binaries for nightly builds + NO_BUILD=true + if [ "${OSARCH}" == "darwin/arm64" ]; then + make universal + OSARCH="darwin/universal" + fi +fi + if [ -z "${NO_BUILD}" ] || [ "${NO_BUILD}" != "true" ]; then scripts/travis/build.sh fi @@ -31,4 +40,4 @@ fi export RELEASE_GENESIS_PROCESS=true export NO_BUILD=true export SkipCleanCheck=1 -scripts/deploy_version.sh "${TRAVIS_BRANCH}" "$(./scripts/osarchtype.sh)" +scripts/deploy_version.sh "${TRAVIS_BRANCH}" "${OSARCH}" diff --git a/test/muleCI/mule.yaml b/test/muleCI/mule.yaml index afa73ae2c1..5022cba792 100644 --- a/test/muleCI/mule.yaml +++ b/test/muleCI/mule.yaml @@ -93,11 +93,8 @@ agents: tasks: - task: shell.Make - name: build.darwin-arm64 - target: ci-build - - task: shell.Make - name: build.darwin-amd64 - target: ci-build + name: build.darwin-universal + target: ci-build-universal - task: docker.Make name: build.amd64 agent: cicd.ubuntu.amd64 @@ -124,9 +121,9 @@ tasks: # Stash tasks - task: stash.Stash - name: darwin-arm64 + name: darwin-universal bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/darwin-arm64 + stashId: ${JENKINS_JOB_CACHE_ID}/darwin-universal globSpecs: - tmp/node_pkgs/**/* - task: stash.Stash @@ -135,12 +132,6 @@ tasks: stashId: ${JENKINS_JOB_CACHE_ID}/linux-amd64 globSpecs: - tmp/node_pkgs/**/* - - task: stash.Stash - name: darwin-amd64 - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/darwin-amd64 - globSpecs: - - tmp/node_pkgs/**/* - task: stash.Stash name: linux-arm64 bucketName: go-algorand-ci-cache @@ -164,13 +155,9 @@ tasks: bucketName: go-algorand-ci-cache stashId: ${JENKINS_JOB_CACHE_ID}/linux-amd64 - task: stash.Unstash - name: darwin-amd64 + name: darwin-universal bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/darwin-amd64 - - task: stash.Unstash - name: darwin-arm64 - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/darwin-arm64 + stashId: ${JENKINS_JOB_CACHE_ID}/darwin-universal - task: stash.Unstash name: packages bucketName: go-algorand-ci-cache @@ -187,14 +174,10 @@ tasks: target: mule-sign jobs: - build-darwin-arm64: - tasks: - - shell.Make.build.darwin-arm64 - - stash.Stash.darwin-arm64 - build-darwin-amd64: + build-darwin-universal: tasks: - - shell.Make.build.darwin-amd64 - - stash.Stash.darwin-amd64 + - shell.Make.build.darwin-universal + - stash.Stash.darwin-universal build-linux-amd64: tasks: - docker.Make.build.amd64 @@ -207,8 +190,7 @@ jobs: tasks: - stash.Unstash.linux-amd64 - stash.Unstash.linux-arm64 - - stash.Unstash.darwin-arm64 - - stash.Unstash.darwin-amd64 + - stash.Unstash.darwin-universal - docker.Make.deb.amd64 - docker.Make.rpm.amd64 - stash.Stash.packages diff --git a/util/s3/s3Helper.go b/util/s3/s3Helper.go index efad67d924..79396e7ced 100644 --- a/util/s3/s3Helper.go +++ b/util/s3/s3Helper.go @@ -189,7 +189,17 @@ func (helper *Helper) GetPackageVersion(channel string, pkg string, specificVers osName := runtime.GOOS arch := runtime.GOARCH prefix := fmt.Sprintf("%s_%s_%s-%s_", pkg, channel, osName, arch) - return helper.GetPackageFilesVersion(channel, prefix, specificVersion) + + maxVersion, maxVersionName, err = helper.GetPackageFilesVersion(channel, prefix, specificVersion) + // For darwin, we want to also look at universal binaries + if osName == "darwin" { + universalPrefix := fmt.Sprintf("%s_%s_%s-%s_", pkg, channel, osName, "universal") + universalMaxVersion, universalMaxVersionName, universalErr := helper.GetPackageFilesVersion(channel, universalPrefix, specificVersion) + if universalMaxVersion > maxVersion { + return universalMaxVersion, universalMaxVersionName, universalErr + } + } + return maxVersion, maxVersionName, err } // GetPackageFilesVersion return the package version From 2b34eda7849a99cedbfac55d8ae026cf886f7715 Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 26 Jul 2024 10:37:30 -0400 Subject: [PATCH 182/218] CI: update to CentOS Stream 9 and start integrating universal target (#6080) --- docker/build/cicd.centos.Dockerfile | 28 ------------------- ...os8.Dockerfile => cicd.centos9.Dockerfile} | 11 ++------ package-deploy.yaml | 4 +-- package-test.yaml | 4 +-- package.yaml | 4 +-- scripts/release/README.md | 8 +++--- scripts/release/build/stage/build/task.sh | 4 +-- .../release/common/docker/centos.Dockerfile | 8 ------ ...{centos8.Dockerfile => centos9.Dockerfile} | 5 ++-- scripts/release/common/setup.sh | 3 +- ...ntos8_image.sh => ensure_centos9_image.sh} | 6 ++-- scripts/release/mule/package/rpm/package.sh | 2 +- scripts/release/prod/rpm/run_centos.sh | 4 +-- scripts/release/test/rpm/run_centos.sh | 4 +-- scripts/release/test/util/test_package.sh | 6 ++-- test/muleCI/mule.yaml | 24 ++++------------ .../test_linux_amd64_compatibility.sh | 6 ++-- 17 files changed, 38 insertions(+), 93 deletions(-) delete mode 100644 docker/build/cicd.centos.Dockerfile rename docker/build/{cicd.centos8.Dockerfile => cicd.centos9.Dockerfile} (79%) delete mode 100644 scripts/release/common/docker/centos.Dockerfile rename scripts/release/common/docker/{centos8.Dockerfile => centos9.Dockerfile} (77%) rename scripts/release/mule/common/{ensure_centos8_image.sh => ensure_centos9_image.sh} (69%) diff --git a/docker/build/cicd.centos.Dockerfile b/docker/build/cicd.centos.Dockerfile deleted file mode 100644 index f292e3d220..0000000000 --- a/docker/build/cicd.centos.Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -ARG ARCH="amd64" - -FROM ${ARCH}/centos:7 -ARG GOLANG_VERSION -ARG ARCH="amd64" -RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && \ - yum update -y && \ - yum install -y autoconf wget awscli git gnupg2 nfs-utils python3-devel expect jq \ - libtool gcc-c++ libstdc++-devel libstdc++-static rpmdevtools createrepo rpm-sign bzip2 which ShellCheck \ - libffi-devel openssl-devel -WORKDIR /root -RUN wget https://dl.google.com/go/go${GOLANG_VERSION}.linux-${ARCH%v*}.tar.gz \ - && tar -xvf go${GOLANG_VERSION}.linux-${ARCH%v*}.tar.gz && \ - mv go /usr/local -ENV GOROOT=/usr/local/go \ - GOPATH=$HOME/go \ - ARCH_TYPE=${ARCH} -RUN mkdir -p $GOPATH/src/github.com/algorand -COPY . $GOPATH/src/github.com/algorand/go-algorand -ENV PATH=$GOPATH/bin:$GOROOT/bin:$PATH \ - GOPROXY=https://proxy.golang.org,https://pkg.go.dev,https://goproxy.io,direct -WORKDIR $GOPATH/src/github.com/algorand/go-algorand -RUN git config --global --add safe.directory '*' -RUN make clean -RUN rm -rf $GOPATH/src/github.com/algorand/go-algorand && \ - mkdir -p $GOPATH/src/github.com/algorand/go-algorand -RUN echo "vm.max_map_count = 262144" >> /etc/sysctl.conf -CMD ["/bin/bash"] diff --git a/docker/build/cicd.centos8.Dockerfile b/docker/build/cicd.centos9.Dockerfile similarity index 79% rename from docker/build/cicd.centos8.Dockerfile rename to docker/build/cicd.centos9.Dockerfile index 76ec3e9cc5..e0d53e467e 100644 --- a/docker/build/cicd.centos8.Dockerfile +++ b/docker/build/cicd.centos9.Dockerfile @@ -1,18 +1,13 @@ ARG ARCH="amd64" -FROM quay.io/centos/centos:stream8 +FROM quay.io/centos/centos:stream9 ARG GOLANG_VERSION ARG ARCH="amd64" -RUN dnf update rpm -y && \ - dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \ +RUN dnf install -y epel-release epel-next-release && dnf config-manager --set-enabled crb && \ dnf update -y && \ dnf install -y autoconf wget awscli git gnupg2 nfs-utils python3-devel expect jq \ libtool gcc-c++ libstdc++-devel rpmdevtools createrepo rpm-sign bzip2 which \ - libffi-devel openssl-devel -RUN dnf install -y epel-release && \ - dnf update && \ - dnf -y --enablerepo=powertools install libstdc++-static && \ - dnf -y install make + libffi-devel openssl-devel libstdc++-static RUN echo "${BOLD}Downloading and installing binaries...${RESET}" && \ curl -Of https://shellcheck.storage.googleapis.com/shellcheck-v0.7.0.linux.x86_64.tar.xz && \ tar -C /usr/local/bin/ -xf shellcheck-v0.7.0.linux.x86_64.tar.xz --no-anchored 'shellcheck' --strip=1 diff --git a/package-deploy.yaml b/package-deploy.yaml index 9b67a2fe6c..b4da6c2eec 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.centos8.Dockerfile - image: algorand/go-algorand-ci-linux-centos8 + dockerFilePath: docker/build/cicd.centos9.Dockerfile + image: algorand/go-algorand-ci-linux-centos9 version: scripts/configure_dev-deps.sh buildArgs: - GOLANG_VERSION=`./scripts/get_golang_version.sh` diff --git a/package-test.yaml b/package-test.yaml index e338197ff1..cd526dda68 100644 --- a/package-test.yaml +++ b/package-test.yaml @@ -16,8 +16,8 @@ agents: workDir: $HOME/projects/go-algorand - name: rpm - dockerFilePath: docker/build/cicd.centos.Dockerfile - image: algorand/mule-linux-centos + dockerFilePath: docker/build/cicd.centos9.Dockerfile + image: algorand/mule-linux-centos9 version: scripts/configure_dev-deps.sh buildArgs: - GOLANG_VERSION=`./scripts/get_golang_version.sh` diff --git a/package.yaml b/package.yaml index d5d8de0d97..0cc1d588eb 100644 --- a/package.yaml +++ b/package.yaml @@ -11,8 +11,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.centos9.Dockerfile + image: algorand/go-algorand-ci-linux-centos9 version: scripts/configure_dev-deps.sh buildArgs: - GOLANG_VERSION=`./scripts/get_golang_version.sh` diff --git a/scripts/release/README.md b/scripts/release/README.md index f3fa543f13..534bc70faa 100644 --- a/scripts/release/README.md +++ b/scripts/release/README.md @@ -50,7 +50,7 @@ This section briefly describes the expected outcomes of the current build pipeli 1. build - 1. Build (compile) the binaries in a Centos 7 & 8 docker container that will then be used by both `deb` and `rpm` packaging. + 1. Build (compile) the binaries in a Centos 9 docker container that will then be used by both `deb` and `rpm` packaging. 1. Docker containers will package `deb` and `rpm` artifacts inside of Ubuntu 20.04 and Centos 7 & 8, respectively. @@ -69,9 +69,9 @@ This section briefly describes the expected outcomes of the current build pipeli - The signatures are correct. - The packages are built from the correct branch and channel and are the correct version. This done by running `algod -v`. + This is done for the following docker containers: - - centos:7 - - quay.io/centos/centos:stream8 - - fedora:38 + - quay.io/centos/centos:stream9 + - fedora:39 + - fedora:40 - ubuntu:20.04 - ubuntu:22.04 diff --git a/scripts/release/build/stage/build/task.sh b/scripts/release/build/stage/build/task.sh index fdb96d6b27..944aeb7b34 100755 --- a/scripts/release/build/stage/build/task.sh +++ b/scripts/release/build/stage/build/task.sh @@ -30,8 +30,8 @@ else echo ${BUILD_NUMBER} > "${REPO_ROOT}"/buildnumber.dat fi -# Run RPM build in Centos 7 & 8 Docker container -sg docker "docker build -t algocentosbuild - < $HOME/go/src/github.com/algorand/go-algorand/scripts/release/common/docker/centos.Dockerfile" +# Run RPM build in Centos 9 Docker container +sg docker "docker build -t algocentosbuild - < $HOME/go/src/github.com/algorand/go-algorand/scripts/release/common/docker/centos9.Dockerfile" sg docker "docker run --rm --env-file ${HOME}/build_env_docker --mount type=bind,src=${HOME},dst=/root/subhome algocentosbuild /root/subhome/go/src/github.com/algorand/go-algorand/scripts/release/build/rpm/build.sh" echo diff --git a/scripts/release/common/docker/centos.Dockerfile b/scripts/release/common/docker/centos.Dockerfile deleted file mode 100644 index a23b446ca1..0000000000 --- a/scripts/release/common/docker/centos.Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM centos:7 - -WORKDIR /root -RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -RUN yum install -y autoconf awscli curl git gnupg2 nfs-utils python36 expect jq libtool gcc-c++ libstdc++-devel libstdc++-static rpmdevtools createrepo rpm-sign bzip2 which ShellCheck - -ENTRYPOINT ["/bin/bash"] - diff --git a/scripts/release/common/docker/centos8.Dockerfile b/scripts/release/common/docker/centos9.Dockerfile similarity index 77% rename from scripts/release/common/docker/centos8.Dockerfile rename to scripts/release/common/docker/centos9.Dockerfile index cf5474cfe7..1151201edb 100644 --- a/scripts/release/common/docker/centos8.Dockerfile +++ b/scripts/release/common/docker/centos9.Dockerfile @@ -1,7 +1,8 @@ -FROM quay.io/centos/centos:stream8 +FROM quay.io/centos/centos:stream9 WORKDIR /root -RUN dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \ +RUN dnf install -y epel-release epel-next-release && dnf config-manager --set-enabled crb && \ + dnf update -y && \ dnf install -y autoconf awscli curl git gnupg2 nfs-utils python36 expect jq libtool gcc-c++ libstdc++-devel rpmdevtools createrepo rpm-sign bzip2 which && \ dnf -y --enablerepo=powertools install libstdc++-static diff --git a/scripts/release/common/setup.sh b/scripts/release/common/setup.sh index 75683262d5..6124e0b95f 100755 --- a/scripts/release/common/setup.sh +++ b/scripts/release/common/setup.sh @@ -104,8 +104,7 @@ else fi sudo usermod -a -G docker ubuntu -sg docker "docker pull centos:7" -sg docker "docker pull quay.io/centos/centos:stream8" +sg docker "docker pull quay.io/centos/centos:stream9" sg docker "docker pull ubuntu:22.04" cat << EOF >> "${HOME}/.bashrc" diff --git a/scripts/release/mule/common/ensure_centos8_image.sh b/scripts/release/mule/common/ensure_centos9_image.sh similarity index 69% rename from scripts/release/mule/common/ensure_centos8_image.sh rename to scripts/release/mule/common/ensure_centos9_image.sh index 1ebd3475fe..bb03624c1b 100755 --- a/scripts/release/mule/common/ensure_centos8_image.sh +++ b/scripts/release/mule/common/ensure_centos9_image.sh @@ -2,9 +2,9 @@ set -exo pipefail -# Ensure the centos8 docker image is built and available +# Ensure the centos docker image is built and available -DOCKER_IMAGE="algorand/go-algorand-ci-linux-centos8:amd64-$(sha1sum scripts/configure_dev-deps.sh | cut -f1 -d' ')" +DOCKER_IMAGE="algorand/go-algorand-ci-linux-centos9:amd64-$(sha1sum scripts/configure_dev-deps.sh | cut -f1 -d' ')" MATCH=${DOCKER_IMAGE/:*/} echo "Checking for RPM image" @@ -13,5 +13,5 @@ if docker images $DOCKER_IMAGE | grep -qs $MATCH > /dev/null 2>&1; then else echo "RPM image doesn't exist, building" docker build --platform=linux/amd64 --build-arg ARCH=amd64 \ - --build-arg GOLANG_VERSION=$(./scripts/get_golang_version.sh) -t $DOCKER_IMAGE -f docker/build/cicd.centos8.Dockerfile . + --build-arg GOLANG_VERSION=$(./scripts/get_golang_version.sh) -t $DOCKER_IMAGE -f docker/build/cicd.centos9.Dockerfile . fi diff --git a/scripts/release/mule/package/rpm/package.sh b/scripts/release/mule/package/rpm/package.sh index f3e3dde17f..7d63872f5c 100755 --- a/scripts/release/mule/package/rpm/package.sh +++ b/scripts/release/mule/package/rpm/package.sh @@ -50,7 +50,7 @@ find tmp/node_pkgs -name "*${CHANNEL}*linux*${VERSION}*.tar.gz" | cut -d '/' -f3 -e "s,@REQUIRED_ALGORAND_PKG@,$REQUIRED_ALGORAND_PACKAGE," \ > "$TEMPDIR/$ALGORAND_PACKAGE_NAME.spec" - rpmbuild --buildroot "$HOME/foo" --define "_rpmdir $RPMTMP" --define "RELEASE_GENESIS_PROCESS xtrue" --define "LICENSE_FILE ./COPYING" -bb "$TEMPDIR/$ALGORAND_PACKAGE_NAME.spec" --target $ARCH_UNAME + rpmbuild --buildroot "$HOME/foo" --define "_rpmdir $RPMTMP" --define "RELEASE_GENESIS_PROCESS \"xtrue\"" --define "LICENSE_FILE ./COPYING" -bb "$TEMPDIR/$ALGORAND_PACKAGE_NAME.spec" --target $ARCH_UNAME cp -p "$RPMTMP"/*/*.rpm "./tmp/node_pkgs/$OS_TYPE/$ARCH_TYPE" echo "${RPMTMP}" diff --git a/scripts/release/prod/rpm/run_centos.sh b/scripts/release/prod/rpm/run_centos.sh index 9426da5ea1..abb2b73f9e 100755 --- a/scripts/release/prod/rpm/run_centos.sh +++ b/scripts/release/prod/rpm/run_centos.sh @@ -5,8 +5,8 @@ set -ex . "${HOME}"/build_env -# Run RPM build in Centos 7 & 8 Docker container -sg docker "docker build -t algocentosbuild - < ${HOME}/go/src/github.com/algorand/go-algorand/scripts/release/common/docker/centos.Dockerfile" +# Run RPM build in Centos 9 Docker container +sg docker "docker build -t algocentosbuild - < ${HOME}/go/src/github.com/algorand/go-algorand/scripts/release/common/docker/centos9.Dockerfile" sg docker "docker run --rm --env-file ${HOME}/build_env_docker --mount type=bind,src=/run/user/1000/gnupg/S.gpg-agent,dst=/root/S.gpg-agent --mount type=bind,src=${HOME}/prodrepo,dst=/root/prodrepo --mount type=bind,src=${HOME}/keys,dst=/root/keys --mount type=bind,src=${HOME},dst=/root/subhome algocentosbuild /root/subhome/go/src/github.com/algorand/go-algorand/scripts/release/prod/rpm/snapshot.sh" diff --git a/scripts/release/test/rpm/run_centos.sh b/scripts/release/test/rpm/run_centos.sh index 88e0a6be4c..d206a085d7 100755 --- a/scripts/release/test/rpm/run_centos.sh +++ b/scripts/release/test/rpm/run_centos.sh @@ -14,8 +14,8 @@ if [ "$CHANNEL" = beta ]; then exit 0 fi -# Run RPM build in Centos 7 & 8 Docker container -sg docker "docker build -t algocentosbuild - < ${HOME}/go/src/github.com/algorand/go-algorand/scripts/release/common/docker/centos.Dockerfile" +# Run RPM build in Centos 9 Docker container +sg docker "docker build -t algocentosbuild - < ${HOME}/go/src/github.com/algorand/go-algorand/scripts/release/common/docker/centos9.Dockerfile" cat <"${HOME}"/dummyrepo/algodummy.repo [algodummy] diff --git a/scripts/release/test/util/test_package.sh b/scripts/release/test/util/test_package.sh index 61c93b84b4..c8dd206c8c 100755 --- a/scripts/release/test/util/test_package.sh +++ b/scripts/release/test/util/test_package.sh @@ -8,9 +8,9 @@ set -ex . "${HOME}"/build_env OS_LIST=( - centos:7 - quay.io/centos/centos:stream8 - fedora:38 + quay.io/centos/centos:stream9 + fedora:39 + fedora:40 ubuntu:20.04 ubuntu:22.04 ) diff --git a/test/muleCI/mule.yaml b/test/muleCI/mule.yaml index 5022cba792..e1ce86b9f8 100644 --- a/test/muleCI/mule.yaml +++ b/test/muleCI/mule.yaml @@ -15,23 +15,9 @@ agents: - GOLANG_VERSION=`./scripts/get_golang_version.sh` - ARCH=amd64 - GOARCH=amd64 - - name: cicd.centos.amd64 - dockerFilePath: docker/build/cicd.centos.Dockerfile - image: algorand/go-algorand-ci-linux-centos - version: scripts/configure_dev-deps.sh - arch: amd64 - env: - - TRAVIS_BRANCH=${GIT_BRANCH} - - NETWORK=$NETWORK - - VERSION=$VERSION - - BUILD_NUMBER=$BUILD_NUMBER - - GOHOSTARCH=amd64 - buildArgs: - - GOLANG_VERSION=`./scripts/get_golang_version.sh` - - ARCH=amd64 - - name: cicd.centos8.amd64 - dockerFilePath: docker/build/cicd.centos8.Dockerfile - image: algorand/go-algorand-ci-linux-centos8 + - name: cicd.centos9.amd64 + dockerFilePath: docker/build/cicd.centos9.Dockerfile + image: algorand/go-algorand-ci-linux-centos9 version: scripts/configure_dev-deps.sh arch: amd64 env: @@ -106,12 +92,12 @@ tasks: - task: docker.Make name: archive - agent: cicd.centos8.amd64 + agent: cicd.centos9.amd64 target: archive - task: docker.Make name: rpm.amd64 - agent: cicd.centos.amd64 + agent: cicd.centos9.amd64 target: mule-package-rpm - task: docker.Make diff --git a/test/platform/test_linux_amd64_compatibility.sh b/test/platform/test_linux_amd64_compatibility.sh index 7e91c8728e..2ab8d4c990 100755 --- a/test/platform/test_linux_amd64_compatibility.sh +++ b/test/platform/test_linux_amd64_compatibility.sh @@ -7,9 +7,9 @@ BLUE_FG=$(tput setaf 4 2>/dev/null) END_FG_COLOR=$(tput sgr0 2>/dev/null) OS_LIST=( - centos:7 - quay.io/centos/centos:stream8 - fedora:38 + quay.io/centos/centos:stream9 + fedora:39 + fedora:40 ubuntu:20.04 ubuntu:22.04 ) From adaecdeb786c32a495d0d4a755749db3968c5113 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:17:56 -0400 Subject: [PATCH 183/218] p2p: fix connection deduplication in hybrid mode (#6082) --- network/p2pNetwork.go | 1 + network/p2pNetwork_test.go | 55 +++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 9f5448c0ea..9417b641ae 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -797,6 +797,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea networkPeerIdentityDisconnect.Inc(nil) n.log.With("remote", addr).With("local", localAddr).Warn("peer deduplicated before adding because the identity is already known") stream.Close() + return } wsp.init(n.config, outgoingMessagesBufferSize) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 0eac398431..9fc00774fb 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -31,6 +31,7 @@ import ( "time" "github.com/algorand/go-algorand/config" + algocrypto "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network/limitcaller" "github.com/algorand/go-algorand/network/p2p" @@ -1081,7 +1082,7 @@ func TestP2PWantTXGossip(t *testing.T) { require.True(t, net.wantTXGossip.Load()) } -func TestMergeP2PAddrInfoResolvedAddresses(t *testing.T) { +func TestP2PMergeAddrInfoResolvedAddresses(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -1154,3 +1155,55 @@ func TestMergeP2PAddrInfoResolvedAddresses(t *testing.T) { }) } } + +// TestP2PwsStreamHandlerDedup checks that the wsStreamHandler detects duplicate connections +// and does not add a new wePeer for it. +func TestP2PwsStreamHandlerDedup(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses + cfg.NetAddress = "127.0.0.1:0" + log := logging.TestingLog(t) + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, &identityOpts{tracker: NewIdentityTracker()}) + require.NoError(t, err) + err = netA.Start() + require.NoError(t, err) + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsA[0]) + + multiAddrStr := addrsA[0].String() + phoneBookAddresses := []string{multiAddrStr} + netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, &identityOpts{tracker: NewIdentityTracker()}) + require.NoError(t, err) + + // now say netA's identity tracker knows about netB's peerID + var netIdentPeerID algocrypto.PublicKey + p2pPeerPubKey, err := netB.service.ID().ExtractPublicKey() + require.NoError(t, err) + + b, err := p2pPeerPubKey.Raw() + require.NoError(t, err) + netIdentPeerID = algocrypto.PublicKey(b) + wsp := &wsPeer{ + identity: netIdentPeerID, + } + netA.identityTracker.setIdentity(wsp) + networkPeerIdentityDisconnectInitial := networkPeerIdentityDisconnect.GetUint64Value() + + // start network and ensure dedup happens + err = netB.Start() + require.NoError(t, err) + defer netB.Stop() + + require.Eventually(t, func() bool { + return networkPeerIdentityDisconnect.GetUint64Value() == networkPeerIdentityDisconnectInitial+1 + }, 2*time.Second, 50*time.Millisecond) + + require.False(t, netA.hasPeers()) + require.False(t, netB.hasPeers()) +} From e52f91a4b1025f78ffe858f77e15d32b3157bf43 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:18:32 -0400 Subject: [PATCH 184/218] txHandler: fix TestTxHandlerAppRateLimiter (#6075) --- data/appRateLimiter.go | 4 ++-- data/appRateLimiter_test.go | 2 +- data/txHandler_test.go | 47 ++++++++++++++++++++----------------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/data/appRateLimiter.go b/data/appRateLimiter.go index 1f4472e68a..9bb2fd1254 100644 --- a/data/appRateLimiter.go +++ b/data/appRateLimiter.go @@ -81,8 +81,8 @@ func makeAppRateLimiter(maxCacheSize int, maxAppPeerRate uint64, serviceRateWind serviceRatePerWindow := maxAppPeerRate * uint64(serviceRateWindow/time.Second) maxBucketSize := maxCacheSize / numBuckets if maxBucketSize == 0 { - // got the max size less then buckets, use maps of 1 - maxBucketSize = 1 + // got the max size less then buckets, use maps of 2 to avoid eviction on each insert + maxBucketSize = 2 } r := &appRateLimiter{ maxBucketSize: maxBucketSize, diff --git a/data/appRateLimiter_test.go b/data/appRateLimiter_test.go index f5e63dfb36..5a7a872133 100644 --- a/data/appRateLimiter_test.go +++ b/data/appRateLimiter_test.go @@ -40,7 +40,7 @@ func TestAppRateLimiter_Make(t *testing.T) { window := 1 * time.Second rm := makeAppRateLimiter(10, rate, window) - require.Equal(t, 1, rm.maxBucketSize) + require.Equal(t, 2, rm.maxBucketSize) require.NotEmpty(t, rm.seed) require.NotEmpty(t, rm.salt) for i := 0; i < len(rm.buckets); i++ { diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 9237865037..23235f15fd 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -2515,8 +2515,14 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + // technically we don't need any users for this test + // but we need to create the genesis accounts to prevent this warning: + // "cannot start evaluator: overflowed subtracting rewards for block 1" + _, _, genesis := makeTestGenesisAccounts(t, 0) + genBal := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr) ledgerName := fmt.Sprintf("%s-mem", t.Name()) const inMem = true + log := logging.TestingLog(t) log.SetLevel(logging.Panic) @@ -2525,11 +2531,9 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { cfg.TxBacklogServiceRateWindowSeconds = 1 cfg.TxBacklogAppTxPerSecondRate = 3 cfg.TxBacklogSize = 3 - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, bookkeeping.GenesisBalances{}, genesisID, genesisHash, cfg) + l, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) - defer ledger.Close() - - l := ledger + defer l.Close() func() { cfg.EnableTxBacklogRateLimiting = false @@ -2618,9 +2622,10 @@ func TestTxHandlerAppRateLimiterERLEnabled(t *testing.T) { require.Equal(t, 1, handler.appLimiter.len()) } +// TestTxHandlerAppRateLimiter submits few app txns to make the app rate limit to filter one the last txn +// to ensure it is propely integrated with the txHandler func TestTxHandlerAppRateLimiter(t *testing.T) { partitiontest.PartitionTest(t) - t.Parallel() const numUsers = 10 log := logging.TestingLog(t) @@ -2637,16 +2642,16 @@ func TestTxHandlerAppRateLimiter(t *testing.T) { cfg.TxBacklogAppTxRateLimiterMaxSize = 100 cfg.TxBacklogServiceRateWindowSeconds = 1 cfg.TxBacklogAppTxPerSecondRate = 3 - ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) + l, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) require.NoError(t, err) - defer ledger.Close() + defer l.Close() - l := ledger handler, err := makeTestTxHandler(l, cfg) require.NoError(t, err) defer handler.txVerificationPool.Shutdown() defer close(handler.streamVerifierDropped) + handler.appLimiterBacklogThreshold = -1 // force the rate limiter to start checking transactions tx := transactions.Transaction{ Type: protocol.ApplicationCallTx, Header: transactions.Header{ @@ -2667,21 +2672,21 @@ func TestTxHandlerAppRateLimiter(t *testing.T) { require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) require.Equal(t, 1, len(handler.backlogQueue)) + counterBefore := transactionMessagesAppLimiterDrop.GetUint64Value() // trigger the rate limiter and ensure the txn is ignored - tx2 := tx - for i := 0; i < cfg.TxBacklogAppTxPerSecondRate*cfg.TxBacklogServiceRateWindowSeconds; i++ { - tx2.ForeignApps = append(tx2.ForeignApps, 1) + numTxnToTriggerARL := cfg.TxBacklogAppTxPerSecondRate * cfg.TxBacklogServiceRateWindowSeconds + for i := 0; i < numTxnToTriggerARL; i++ { + tx2 := tx + tx2.Header.Sender = addresses[i+1] + signedTx2 := tx2.Sign(secrets[i+1]) + blob2 := protocol.Encode(&signedTx2) + + action = handler.processIncomingTxn(network.IncomingMessage{Data: blob2, Sender: mockSender{}}) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) } - signedTx2 := tx.Sign(secrets[1]) - blob2 := protocol.Encode(&signedTx2) - - action = handler.processIncomingTxn(network.IncomingMessage{Data: blob2, Sender: mockSender{}}) - require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) - require.Equal(t, 1, len(handler.backlogQueue)) - - // backlogQueue has the first txn, but the second one is dropped - msg := <-handler.backlogQueue - require.Equal(t, msg.rawmsg.Data, blob, blob) + // last txn should be dropped + require.Equal(t, 1+numTxnToTriggerARL-1, len(handler.backlogQueue)) + require.Equal(t, counterBefore+1, transactionMessagesAppLimiterDrop.GetUint64Value()) } // TestTxHandlerCapGuard checks there is no cap guard leak in case of invalid input. From 8ed60e2c88e8c0296e7424a4c40ec39ab88eb6c5 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:50:59 -0400 Subject: [PATCH 185/218] tests: fix TestNodeHybridTopology (#6079) --- node/node_test.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index e17e3e8d3f..df72a699bb 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -841,16 +841,13 @@ func TestNodeHybridTopology(t *testing.T) { testParams0.AgreementFilterTimeoutPeriod0 = 500 * time.Millisecond configurableConsensus[consensusTest0] = testParams0 - minMoneyAtStart := 1_000_000 - maxMoneyAtStart := 100_000_000_000 - gen := rand.New(rand.NewSource(2)) - + // configure the stake to have R and A producing and confirming blocks + const totalStake = 100_000_000_000 const numAccounts = 3 acctStake := make([]basics.MicroAlgos, numAccounts) - for i := range acctStake { - acctStake[i] = basics.MicroAlgos{Raw: uint64(minMoneyAtStart + (gen.Int() % (maxMoneyAtStart - minMoneyAtStart)))} - } acctStake[0] = basics.MicroAlgos{} // no stake at node 0 + acctStake[1] = basics.MicroAlgos{Raw: uint64(totalStake / 2)} + acctStake[2] = basics.MicroAlgos{Raw: uint64(totalStake / 2)} configHook := func(ni nodeInfo, cfg config.Local) (nodeInfo, config.Local) { cfg = config.GetDefaultLocal() @@ -918,9 +915,18 @@ func TestNodeHybridTopology(t *testing.T) { startAndConnectNodes(nodes, 10*time.Second) + // ensure the initial connectivity topology + require.Eventually(t, func() bool { + node0Conn := len(nodes[0].net.GetPeers(network.PeersConnectedIn)) > 0 // has connection from 1 + node1Conn := len(nodes[1].net.GetPeers(network.PeersConnectedOut, network.PeersConnectedIn)) == 2 // connected to 0 and 2 + node2Conn := len(nodes[2].net.GetPeers(network.PeersConnectedOut, network.PeersConnectedIn)) >= 1 // connected to 1 + return node0Conn && node1Conn && node2Conn + }, 60*time.Second, 500*time.Millisecond) + initialRound := nodes[0].ledger.NextRound() targetRound := initialRound + 10 + // ensure discovery of archival node by tracking its ledger select { case <-nodes[0].ledger.Wait(targetRound): e0, err := nodes[0].ledger.Block(targetRound) @@ -928,7 +934,7 @@ func TestNodeHybridTopology(t *testing.T) { e1, err := nodes[1].ledger.Block(targetRound) require.NoError(t, err) require.Equal(t, e1.Hash(), e0.Hash()) - case <-time.After(120 * time.Second): + case <-time.After(3 * time.Minute): // set it to 1.5x of the dht.periodicBootstrapInterval to give DHT code to rebuild routing table one more time require.Fail(t, fmt.Sprintf("no block notification for wallet: %v.", wallets[0])) } } From 04ec5f9bb188229a790998e8c2f3bec552eaaaf5 Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 26 Jul 2024 17:49:49 -0400 Subject: [PATCH 186/218] CI: fix mac universal rebuild for nightlies (#6084) --- scripts/travis/build.sh | 14 +++++++++++--- scripts/travis/deploy_packages.sh | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index 3087f63d75..244ff399ef 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -9,12 +9,18 @@ # Examples: scripts/travis/build.sh MAKE_DEBUG_OPTION="" +MAKE_UNIVERSAL_OPTION="" + while [ "$1" != "" ]; do case "$1" in --make_debug) shift MAKE_DEBUG_OPTION="1" ;; + --make_universal) + shift + MAKE_UNIVERSAL_OPTION="1" + ;; *) echo "Unknown option" "$1" exit 1 @@ -75,13 +81,15 @@ set -e scripts/travis/before_build.sh duration "before_build.sh" -if [ "${OS}-${ARCH}" = "linux-arm" ] || [ "${OS}-${ARCH}" = "windows-amd64" ]; then - # for arm, build just the basic distro +if [ "${OS}-${ARCH}" = "windows-amd64" ]; then # for windows, we still have some issues with the enlistment checking, so we'll make it simple for now. MAKE_DEBUG_OPTION="" fi -if [ "${MAKE_DEBUG_OPTION}" != "" ]; then +if [ "${MAKE_UNIVERSAL_OPTION}" != "" ]; then + make universal + duration "make universal" +elif [ "${MAKE_DEBUG_OPTION}" != "" ]; then make build build-race duration "make build build-race" else diff --git a/scripts/travis/deploy_packages.sh b/scripts/travis/deploy_packages.sh index 5879835b01..8eae9307b4 100755 --- a/scripts/travis/deploy_packages.sh +++ b/scripts/travis/deploy_packages.sh @@ -26,11 +26,11 @@ fi if [ "${NIGHTLY_BUILD}" == "true" ]; then # we want to rebuild universal binaries for nightly builds - NO_BUILD=true if [ "${OSARCH}" == "darwin/arm64" ]; then - make universal + ./scripts/travis/build.sh --make_universal OSARCH="darwin/universal" fi + NO_BUILD=true fi if [ -z "${NO_BUILD}" ] || [ "${NO_BUILD}" != "true" ]; then From edda2ee09c1c89c54dafd9e0e898f82760095bf0 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Fri, 26 Jul 2024 17:50:36 -0400 Subject: [PATCH 187/218] rpcs: simplify API for BlockService to handle multiple HTTP paths (#5718) Co-authored-by: Pavel Zbitskiy --- catchup/fetcher_test.go | 7 +++++++ catchup/pref_test.go | 2 +- catchup/service_test.go | 22 +++++++++++----------- catchup/universalFetcher_test.go | 2 +- components/mocks/mockNetwork.go | 4 ++++ network/gossipNode.go | 3 ++- network/hybridNetwork.go | 6 ++++++ network/p2p/http.go | 8 ++++++++ network/p2pNetwork.go | 6 ++++++ network/wsNetwork.go | 5 +++++ rpcs/blockService.go | 17 +++++++++++------ rpcs/blockService_test.go | 19 +++++++++---------- rpcs/registrar.go | 2 ++ rpcs/txService_test.go | 7 +++++++ 14 files changed, 80 insertions(+), 30 deletions(-) diff --git a/catchup/fetcher_test.go b/catchup/fetcher_test.go index 52b0b32a8f..85dcaba70b 100644 --- a/catchup/fetcher_test.go +++ b/catchup/fetcher_test.go @@ -141,6 +141,13 @@ func (b *basicRPCNode) RegisterHTTPHandler(path string, handler http.Handler) { b.rmux.Handle(path, handler) } +func (b *basicRPCNode) RegisterHTTPHandlerFunc(path string, handler func(http.ResponseWriter, *http.Request)) { + if b.rmux == nil { + b.rmux = mux.NewRouter() + } + b.rmux.HandleFunc(path, handler) +} + func (b *basicRPCNode) RegisterHandlers(dispatch []network.TaggedMessageHandler) { } diff --git a/catchup/pref_test.go b/catchup/pref_test.go index a72ed855ec..7c849630e1 100644 --- a/catchup/pref_test.go +++ b/catchup/pref_test.go @@ -50,7 +50,7 @@ func BenchmarkServiceFetchBlocks(b *testing.B) { net := &httpTestPeerSource{} ls := rpcs.MakeBlockService(logging.TestingLog(b), config.GetDefaultLocal(), remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() diff --git a/catchup/service_test.go b/catchup/service_test.go index 045a0438f2..d20305ce82 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -151,7 +151,7 @@ func TestServiceFetchBlocksSameRange(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -223,7 +223,7 @@ func TestSyncRound(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -313,7 +313,7 @@ func TestPeriodicSync(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -379,7 +379,7 @@ func TestServiceFetchBlocksOneBlock(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -443,7 +443,7 @@ func TestAbruptWrites(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -501,7 +501,7 @@ func TestServiceFetchBlocksMultiBlocks(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -555,7 +555,7 @@ func TestServiceFetchBlocksMalformed(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -709,7 +709,7 @@ func helperTestOnSwitchToUnSupportedProtocol( ls := rpcs.MakeBlockService(logging.Base(), config, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -932,7 +932,7 @@ func TestCatchupUnmatchedCertificate(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -1064,7 +1064,7 @@ func TestServiceLedgerUnavailable(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() @@ -1110,7 +1110,7 @@ func TestServiceNoBlockForRound(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, remote, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() diff --git a/catchup/universalFetcher_test.go b/catchup/universalFetcher_test.go index 59c5d69b7a..164360d43d 100644 --- a/catchup/universalFetcher_test.go +++ b/catchup/universalFetcher_test.go @@ -101,7 +101,7 @@ func TestUGetBlockHTTP(t *testing.T) { ls := rpcs.MakeBlockService(logging.Base(), blockServiceConfig, ledger, net, "test genesisID") nodeA := basicRPCNode{} - nodeA.RegisterHTTPHandler(rpcs.BlockServiceBlockPath, ls) + ls.RegisterHandlers(&nodeA) nodeA.start() defer nodeA.stop() rootURL := nodeA.rootURL() diff --git a/components/mocks/mockNetwork.go b/components/mocks/mockNetwork.go index 47b1a5b5e4..4f145b1841 100644 --- a/components/mocks/mockNetwork.go +++ b/components/mocks/mockNetwork.go @@ -103,6 +103,10 @@ func (network *MockNetwork) ClearProcessors() { func (network *MockNetwork) RegisterHTTPHandler(path string, handler http.Handler) { } +// RegisterHTTPHandlerFunc - empty implementation +func (network *MockNetwork) RegisterHTTPHandlerFunc(path string, handler func(http.ResponseWriter, *http.Request)) { +} + // OnNetworkAdvance - empty implementation func (network *MockNetwork) OnNetworkAdvance() {} diff --git a/network/gossipNode.go b/network/gossipNode.go index 1592641f70..8b108b5fde 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -57,8 +57,9 @@ type GossipNode interface { Disconnect(badnode DisconnectablePeer) DisconnectPeers() // only used by testing - // RegisterHTTPHandler path accepts gorilla/mux path annotations + // RegisterHTTPHandler and RegisterHTTPHandlerFunc: path accepts gorilla/mux path annotations RegisterHTTPHandler(path string, handler http.Handler) + RegisterHTTPHandlerFunc(path string, handler func(http.ResponseWriter, *http.Request)) // RequestConnectOutgoing asks the system to actually connect to peers. // `replace` optionally drops existing connections before making new ones. diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go index d30e03cee2..c955dfff1a 100644 --- a/network/hybridNetwork.go +++ b/network/hybridNetwork.go @@ -146,6 +146,12 @@ func (n *HybridP2PNetwork) RegisterHTTPHandler(path string, handler http.Handler n.wsNetwork.RegisterHTTPHandler(path, handler) } +// RegisterHTTPHandlerFunc implements GossipNode +func (n *HybridP2PNetwork) RegisterHTTPHandlerFunc(path string, handlerFunc func(http.ResponseWriter, *http.Request)) { + n.p2pNetwork.RegisterHTTPHandlerFunc(path, handlerFunc) + n.wsNetwork.RegisterHTTPHandlerFunc(path, handlerFunc) +} + // RequestConnectOutgoing implements GossipNode func (n *HybridP2PNetwork) RequestConnectOutgoing(replace bool, quit <-chan struct{}) {} diff --git a/network/p2p/http.go b/network/p2p/http.go index 9f2622d015..07f27afff1 100644 --- a/network/p2p/http.go +++ b/network/p2p/http.go @@ -57,6 +57,14 @@ func (s *HTTPServer) RegisterHTTPHandler(path string, handler http.Handler) { }) } +// RegisterHTTPHandlerFunc registers a http handler with a given path. +func (s *HTTPServer) RegisterHTTPHandlerFunc(path string, handler func(http.ResponseWriter, *http.Request)) { + s.p2phttpMux.HandleFunc(path, handler) + s.p2phttpMuxRegistrarOnce.Do(func() { + s.Host.SetHTTPHandlerAtPath(algorandP2pHTTPProtocol, "/", s.p2phttpMux) + }) +} + // MakeHTTPClient creates a http.Client that uses libp2p transport for a given protocol and peer address. func MakeHTTPClient(addrInfo *peer.AddrInfo) (*http.Client, error) { clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs) diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 9417b641ae..4357970388 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -577,6 +577,12 @@ func (n *P2PNetwork) RegisterHTTPHandler(path string, handler http.Handler) { n.httpServer.RegisterHTTPHandler(path, handler) } +// RegisterHTTPHandlerFunc is like RegisterHTTPHandler but accepts +// a callback handler function instead of a method receiver. +func (n *P2PNetwork) RegisterHTTPHandlerFunc(path string, handler func(http.ResponseWriter, *http.Request)) { + n.httpServer.RegisterHTTPHandlerFunc(path, handler) +} + // RequestConnectOutgoing asks the system to actually connect to peers. // `replace` optionally drops existing connections before making new ones. // `quit` chan allows cancellation. diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 1c0f3e8676..fc42296652 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -523,6 +523,11 @@ func (wn *WebsocketNetwork) RegisterHTTPHandler(path string, handler http.Handle wn.router.Handle(path, handler) } +// RegisterHTTPHandlerFunc path accepts gorilla/mux path annotations +func (wn *WebsocketNetwork) RegisterHTTPHandlerFunc(path string, handler func(http.ResponseWriter, *http.Request)) { + wn.router.HandleFunc(path, handler) +} + // RequestConnectOutgoing tries to actually do the connect to new peers. // `replace` drop all connections first and find new peers. func (wn *WebsocketNetwork) RequestConnectOutgoing(replace bool, quit <-chan struct{}) { diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 1a9893b70a..410376a20f 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -58,7 +58,7 @@ const blockServerCatchupRequestBufferSize = 10 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) +// e.g. .HandleFunc(BlockServiceBlockPath, ls.ServeBlockPath) const BlockServiceBlockPath = "/v{version:[0-9.]+}/{genesisID}/block/{round:[0-9a-z]+}" // Constant strings used as keys for topics @@ -147,11 +147,16 @@ func MakeBlockService(log logging.Logger, config config.Local, ledger LedgerForB memoryCap: config.BlockServiceMemCap, } if service.enableService { - net.RegisterHTTPHandler(BlockServiceBlockPath, service) + service.RegisterHandlers(net) } return service } +// RegisterHandlers registers the request handlers for BlockService's paths with the registrar. +func (bs *BlockService) RegisterHandlers(registrar Registrar) { + registrar.RegisterHTTPHandlerFunc(BlockServiceBlockPath, bs.ServeBlockPath) +} + // Start listening to catchup requests over ws func (bs *BlockService) Start() { bs.mu.Lock() @@ -179,10 +184,10 @@ func (bs *BlockService) Stop() { bs.closeWaitGroup.Wait() } -// ServerHTTP returns blocks +// ServeBlockPath returns blocks // Either /v{version}/{genesisID}/block/{round} or ?b={round}&v={version} // Uses gorilla/mux for path argument parsing. -func (bs *BlockService) ServeHTTP(response http.ResponseWriter, request *http.Request) { +func (bs *BlockService) ServeBlockPath(response http.ResponseWriter, request *http.Request) { pathVars := mux.Vars(request) versionStr, hasVersionStr := pathVars["version"] roundStr, hasRoundStr := pathVars["round"] @@ -260,13 +265,13 @@ func (bs *BlockService) ServeHTTP(response http.ResponseWriter, request *http.Re if !ok { response.Header().Set("Retry-After", blockResponseRetryAfter) response.WriteHeader(http.StatusServiceUnavailable) - bs.log.Debugf("ServeHTTP: returned retry-after: %v", err) + bs.log.Debugf("ServeBlockPath: returned retry-after: %v", err) } httpBlockMessagesDroppedCounter.Inc(nil) return default: // unexpected error. - bs.log.Warnf("ServeHTTP : failed to retrieve block %d %v", round, err) + bs.log.Warnf("ServeBlockPath: failed to retrieve block %d %v", round, err) response.WriteHeader(http.StatusInternalServerError) return } diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index e637796adf..83cfd94ef9 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -166,8 +166,8 @@ func TestRedirectFallbackEndpoints(t *testing.T) { bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID") bs2 := MakeBlockService(log, config, ledger2, net2, "test-genesis-ID") - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) + bs1.RegisterHandlers(nodeA) + bs2.RegisterHandlers(nodeB) parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -210,7 +210,7 @@ func TestBlockServiceShutdown(t *testing.T) { nodeA := &basicRPCNode{} - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + bs1.RegisterHandlers(nodeA) nodeA.start() defer nodeA.stop() @@ -292,9 +292,8 @@ func TestRedirectOnFullCapacity(t *testing.T) { bs1.memoryCap = 250 bs2.memoryCap = 250 - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) - - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) + bs1.RegisterHandlers(nodeA) + bs2.RegisterHandlers(nodeB) parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -371,11 +370,11 @@ forloop: // First node redirects, does not return retry require.True(t, strings.Contains(logBuffer1.String(), "redirectRequest: redirected block request to")) - require.False(t, strings.Contains(logBuffer1.String(), "ServeHTTP: returned retry-after: block service memory over capacity")) + require.False(t, strings.Contains(logBuffer1.String(), "ServeBlockPath: returned retry-after: block service memory over capacity")) // Second node cannot redirect, it returns retry-after when over capacity require.False(t, strings.Contains(logBuffer2.String(), "redirectRequest: redirected block request to")) - require.True(t, strings.Contains(logBuffer2.String(), "ServeHTTP: returned retry-after: block service memory over capacity")) + require.True(t, strings.Contains(logBuffer2.String(), "ServeBlockPath: returned retry-after: block service memory over capacity")) } // TestWsBlockLimiting ensures that limits are applied correctly on the websocket side of the service @@ -474,8 +473,8 @@ func TestRedirectExceptions(t *testing.T) { bs1 := MakeBlockService(log1, configInvalidRedirects, ledger1, net1, "{genesisID}") bs2 := MakeBlockService(log2, configWithRedirectToSelf, ledger2, net2, "{genesisID}") - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) + bs1.RegisterHandlers(nodeA) + bs2.RegisterHandlers(nodeB) parsedURL, err := addr.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) diff --git a/rpcs/registrar.go b/rpcs/registrar.go index f488aebf26..f0122b552c 100644 --- a/rpcs/registrar.go +++ b/rpcs/registrar.go @@ -26,6 +26,8 @@ import ( type Registrar interface { // RegisterHTTPHandler path accepts gorilla/mux path annotations RegisterHTTPHandler(path string, handler http.Handler) + // RegisterHTTPHandlerFunc path accepts gorilla/mux path annotations and a HandlerFunc + RegisterHTTPHandlerFunc(path string, handler func(response http.ResponseWriter, request *http.Request)) // RegisterHandlers exposes global websocket handler registration RegisterHandlers(dispatch []network.TaggedMessageHandler) } diff --git a/rpcs/txService_test.go b/rpcs/txService_test.go index fcfae1044d..0b12d2b413 100644 --- a/rpcs/txService_test.go +++ b/rpcs/txService_test.go @@ -89,6 +89,13 @@ func (b *basicRPCNode) RegisterHTTPHandler(path string, handler http.Handler) { b.rmux.Handle(path, handler) } +func (b *basicRPCNode) RegisterHTTPHandlerFunc(path string, handler func(http.ResponseWriter, *http.Request)) { + if b.rmux == nil { + b.rmux = mux.NewRouter() + } + b.rmux.HandleFunc(path, handler) +} + func (b *basicRPCNode) RegisterHandlers(dispatch []network.TaggedMessageHandler) { } From a9c2b7e6ec8e867e21f357e3edbec08d69090a8e Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:43:38 -0400 Subject: [PATCH 188/218] p2p: do not register closing peer (#6086) Co-authored-by: cce <51567+cce@users.noreply.github.com> --- catchup/universalFetcher_test.go | 15 +++++++-------- network/p2pNetwork.go | 6 ++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/catchup/universalFetcher_test.go b/catchup/universalFetcher_test.go index 164360d43d..bd7f25b77e 100644 --- a/catchup/universalFetcher_test.go +++ b/catchup/universalFetcher_test.go @@ -204,7 +204,7 @@ func TestRequestBlockBytesErrors(t *testing.T) { cancel() _, _, _, err = fetcher.fetchBlock(ctx, next, up) var wrfe errWsFetcherRequestFailed - require.True(t, errors.As(err, &wrfe), "unexpected err: %w", wrfe) + require.ErrorAs(t, err, &wrfe) require.Equal(t, "context canceled", err.(errWsFetcherRequestFailed).cause) ctx = context.Background() @@ -213,14 +213,14 @@ func TestRequestBlockBytesErrors(t *testing.T) { up = makeTestUnicastPeerWithResponseOverride(net, t, &responseOverride) _, _, _, err = fetcher.fetchBlock(ctx, next, up) - require.True(t, errors.As(err, &wrfe)) + require.ErrorAs(t, err, &wrfe) require.Equal(t, "Cert data not found", err.(errWsFetcherRequestFailed).cause) responseOverride = network.Response{Topics: network.Topics{network.MakeTopic(rpcs.CertDataKey, make([]byte, 0))}} up = makeTestUnicastPeerWithResponseOverride(net, t, &responseOverride) _, _, _, err = fetcher.fetchBlock(ctx, next, up) - require.True(t, errors.As(err, &wrfe)) + require.ErrorAs(t, err, &wrfe) require.Equal(t, "Block data not found", err.(errWsFetcherRequestFailed).cause) } @@ -240,7 +240,6 @@ func (thh *TestHTTPHandler) ServeHTTP(response http.ResponseWriter, request *htt bytes = make([]byte, fetcherMaxBlockBytes+1) } response.Write(bytes) - return } // TestGetBlockBytesHTTPErrors tests the errors reported from getblockBytes for http peer @@ -264,25 +263,25 @@ func TestGetBlockBytesHTTPErrors(t *testing.T) { ls.status = http.StatusBadRequest _, _, _, err := fetcher.fetchBlock(context.Background(), 1, net.GetPeers()[0]) var hre errHTTPResponse - require.True(t, errors.As(err, &hre)) + require.ErrorAs(t, err, &hre) require.Equal(t, "Response body '\x00'", err.(errHTTPResponse).cause) ls.exceedLimit = true _, _, _, err = fetcher.fetchBlock(context.Background(), 1, net.GetPeers()[0]) - require.True(t, errors.As(err, &hre)) + require.ErrorAs(t, err, &hre) require.Equal(t, "read limit exceeded", err.(errHTTPResponse).cause) ls.status = http.StatusOK ls.content = append(ls.content, "undefined") _, _, _, err = fetcher.fetchBlock(context.Background(), 1, net.GetPeers()[0]) var cte errHTTPResponseContentType - require.True(t, errors.As(err, &cte)) + require.ErrorAs(t, err, &cte) require.Equal(t, "undefined", err.(errHTTPResponseContentType).contentType) ls.status = http.StatusOK ls.content = append(ls.content, "undefined2") _, _, _, err = fetcher.fetchBlock(context.Background(), 1, net.GetPeers()[0]) - require.True(t, errors.As(err, &cte)) + require.ErrorAs(t, err, &cte) require.Equal(t, 2, err.(errHTTPResponseContentType).contentTypeCount) } diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 4357970388..a186426b15 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -808,6 +808,12 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea wsp.init(n.config, outgoingMessagesBufferSize) n.wsPeersLock.Lock() + if wsp.didSignalClose.Load() == 1 { + networkPeerAlreadyClosed.Inc(nil) + n.log.Debugf("peer closing %s", addr) + n.wsPeersLock.Unlock() + return + } n.wsPeers[p2pPeer] = wsp n.wsPeersToIDs[wsp] = p2pPeer n.wsPeersLock.Unlock() From e697ae8903475e7fc0dcef59e57ef0ebb8b5360b Mon Sep 17 00:00:00 2001 From: John Lee Date: Mon, 29 Jul 2024 16:56:09 -0400 Subject: [PATCH 189/218] CICD: fix broken ci-build target (#6087) --- scripts/release/mule/Makefile.mule | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/mule/Makefile.mule b/scripts/release/mule/Makefile.mule index 06fe5d09b7..6bc0cdb65e 100644 --- a/scripts/release/mule/Makefile.mule +++ b/scripts/release/mule/Makefile.mule @@ -34,7 +34,7 @@ ci-build-universal: ci-clean universal mkdir -p $(PKG_DIR_UNIVERSAL)/data && \ cp installer/genesis/devnet/genesis.json $(PKG_DIR_UNIVERSAL)/data -ci-build: ci-clean +ci-build: ci-clean build mkdir -p $(PKG_DIR) CHANNEL=$(CHANNEL) PKG_ROOT=$(PKG_DIR) NO_BUILD=True VARIATIONS=$(OS_TYPE)-$(ARCH) \ scripts/build_packages.sh $(OS_TYPE)/$(ARCH) && \ From 578684e28266a0ea47c356c811fd689c51070bf3 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:56:43 -0400 Subject: [PATCH 190/218] p2p: support EnableGossipService in p2p streams (#6073) --- network/p2p/p2p.go | 2 +- network/p2p/streams.go | 58 +++++++--------- network/p2pNetwork_test.go | 138 +++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 35 deletions(-) diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 2c64b63eab..21782dce44 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -159,7 +159,7 @@ func configureResourceManager(cfg config.Local) (network.ResourceManager, error) // MakeService creates a P2P service instance func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler, bootstrapPeers []*peer.AddrInfo) (*serviceImpl, error) { - sm := makeStreamManager(ctx, log, h, wsStreamHandler) + sm := makeStreamManager(ctx, log, h, wsStreamHandler, cfg.EnableGossipService) h.Network().Notify(sm) h.SetStreamHandler(AlgorandWsProtocol, sm.streamHandler) diff --git a/network/p2p/streams.go b/network/p2p/streams.go index e7277f4871..0b7838ffdc 100644 --- a/network/p2p/streams.go +++ b/network/p2p/streams.go @@ -30,10 +30,11 @@ import ( // streamManager implements network.Notifiee to create and manage streams for use with non-gossipsub protocols. type streamManager struct { - ctx context.Context - log logging.Logger - host host.Host - handler StreamHandler + ctx context.Context + log logging.Logger + host host.Host + handler StreamHandler + allowIncomingGossip bool streams map[peer.ID]network.Stream streamsLock deadlock.Mutex @@ -42,18 +43,25 @@ type streamManager struct { // StreamHandler is called when a new bidirectional stream for a given protocol and peer is opened. type StreamHandler func(ctx context.Context, pid peer.ID, s network.Stream, incoming bool) -func makeStreamManager(ctx context.Context, log logging.Logger, h host.Host, handler StreamHandler) *streamManager { +func makeStreamManager(ctx context.Context, log logging.Logger, h host.Host, handler StreamHandler, allowIncomingGossip bool) *streamManager { return &streamManager{ - ctx: ctx, - log: log, - host: h, - handler: handler, - streams: make(map[peer.ID]network.Stream), + ctx: ctx, + log: log, + host: h, + handler: handler, + allowIncomingGossip: allowIncomingGossip, + streams: make(map[peer.ID]network.Stream), } } // streamHandler is called by libp2p when a new stream is accepted func (n *streamManager) streamHandler(stream network.Stream) { + if stream.Conn().Stat().Direction == network.DirInbound && !n.allowIncomingGossip { + n.log.Debugf("rejecting stream from incoming connection from %s", stream.Conn().RemotePeer().String()) + stream.Close() + return + } + n.streamsLock.Lock() defer n.streamsLock.Unlock() @@ -74,15 +82,7 @@ func (n *streamManager) streamHandler(stream network.Stream) { } n.streams[stream.Conn().RemotePeer()] = stream - // streamHandler is supposed to be called for accepted streams, so we expect incoming here incoming := stream.Conn().Stat().Direction == network.DirInbound - if !incoming { - if stream.Stat().Direction == network.DirUnknown { - n.log.Warnf("Unknown direction for a steam %s to/from %s", stream.ID(), remotePeer) - } else { - n.log.Warnf("Unexpected outgoing stream in streamHandler for connection %s (%s): %s vs %s stream", stream.Conn().ID(), remotePeer, stream.Conn().Stat().Direction, stream.Stat().Direction.String()) - } - } n.handler(n.ctx, remotePeer, stream, incoming) return } @@ -92,20 +92,18 @@ func (n *streamManager) streamHandler(stream network.Stream) { } // no old stream n.streams[stream.Conn().RemotePeer()] = stream - // streamHandler is supposed to be called for accepted streams, so we expect incoming here incoming := stream.Conn().Stat().Direction == network.DirInbound - if !incoming { - if stream.Stat().Direction == network.DirUnknown { - n.log.Warnf("streamHandler: unknown direction for a steam %s to/from %s", stream.ID(), remotePeer) - } else { - n.log.Warnf("Unexpected outgoing stream in streamHandler for connection %s (%s): %s vs %s stream", stream.Conn().ID(), remotePeer, stream.Conn().Stat().Direction, stream.Stat().Direction.String()) - } - } n.handler(n.ctx, remotePeer, stream, incoming) } // Connected is called when a connection is opened +// for both incoming (listener -> addConn) and outgoing (dialer -> addConn) connections. func (n *streamManager) Connected(net network.Network, conn network.Conn) { + if conn.Stat().Direction == network.DirInbound && !n.allowIncomingGossip { + n.log.Debugf("ignoring incoming connection from %s", conn.RemotePeer().String()) + return + } + remotePeer := conn.RemotePeer() localPeer := n.host.ID() @@ -138,15 +136,7 @@ func (n *streamManager) Connected(net network.Network, conn network.Conn) { needUnlock = false n.streamsLock.Unlock() - // a new stream created above, expected direction is outbound incoming := stream.Conn().Stat().Direction == network.DirInbound - if incoming { - n.log.Warnf("Unexpected incoming stream in streamHandler for connection %s (%s): %s vs %s stream", stream.Conn().ID(), remotePeer, stream.Conn().Stat().Direction, stream.Stat().Direction.String()) - } else { - if stream.Stat().Direction == network.DirUnknown { - n.log.Warnf("Connected: unknown direction for a steam %s to/from %s", stream.ID(), remotePeer) - } - } n.handler(n.ctx, remotePeer, stream, incoming) } diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 9fc00774fb..f6eea2ab69 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -1207,3 +1207,141 @@ func TestP2PwsStreamHandlerDedup(t *testing.T) { require.False(t, netA.hasPeers()) require.False(t, netB.hasPeers()) } + +// TestP2PEnableGossipService_NodeDisable ensures that a node with EnableGossipService=false +// still can participate in the network by sending and receiving messages. +func TestP2PEnableGossipService_NodeDisable(t *testing.T) { + partitiontest.PartitionTest(t) + + log := logging.TestingLog(t) + + // prepare configs + cfg := config.GetDefaultLocal() + cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses + + relayCfg := cfg + relayCfg.NetAddress = "127.0.0.1:0" + + nodeCfg := cfg + nodeCfg.EnableGossipService = false + nodeCfg2 := nodeCfg + nodeCfg2.NetAddress = "127.0.0.1:0" + + tests := []struct { + name string + relayCfg config.Local + nodeCfg config.Local + }{ + {"non-listening-node", relayCfg, nodeCfg}, + {"listening-node", relayCfg, nodeCfg2}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + relayCfg := test.relayCfg + netA, err := NewP2PNetwork(log, relayCfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) + require.NoError(t, err) + netA.Start() + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsA[0]) + multiAddrStr := addrsA[0].String() + phoneBookAddresses := []string{multiAddrStr} + + // start netB with gossip service disabled + nodeCfg := test.nodeCfg + netB, err := NewP2PNetwork(log, nodeCfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) + require.NoError(t, err) + netB.Start() + defer netB.Stop() + + require.Eventually(t, func() bool { + return netA.hasPeers() && netB.hasPeers() + }, 1*time.Second, 50*time.Millisecond) + + testTag := protocol.AgreementVoteTag + + var handlerCountA atomic.Uint32 + passThroughHandlerA := []TaggedMessageHandler{ + {Tag: testTag, MessageHandler: HandlerFunc(func(msg IncomingMessage) OutgoingMessage { + handlerCountA.Add(1) + return OutgoingMessage{Action: Broadcast} + })}, + } + var handlerCountB atomic.Uint32 + passThroughHandlerB := []TaggedMessageHandler{ + {Tag: testTag, MessageHandler: HandlerFunc(func(msg IncomingMessage) OutgoingMessage { + handlerCountB.Add(1) + return OutgoingMessage{Action: Broadcast} + })}, + } + netA.RegisterHandlers(passThroughHandlerA) + netB.RegisterHandlers(passThroughHandlerB) + + // send messages from both nodes to each other and confirm they are received. + for i := 0; i < 10; i++ { + err = netA.Broadcast(context.Background(), testTag, []byte(fmt.Sprintf("hello from A %d", i)), false, nil) + require.NoError(t, err) + err = netB.Broadcast(context.Background(), testTag, []byte(fmt.Sprintf("hello from B %d", i)), false, nil) + require.NoError(t, err) + } + + require.Eventually( + t, + func() bool { + return handlerCountA.Load() == 10 && handlerCountB.Load() == 10 + }, + 2*time.Second, + 50*time.Millisecond, + ) + }) + } +} + +// TestP2PEnableGossipService_BothDisable checks if both relay and node have EnableGossipService=false +// they do not gossip to each other. +// +// Note, this test checks a configuration where node A (relay) does not know about node B, +// and node B is configured to connect to A, and this scenario rejecting logic is guaranteed to work. +func TestP2PEnableGossipService_BothDisable(t *testing.T) { + partitiontest.PartitionTest(t) + + log := logging.TestingLog(t) + + // prepare configs + cfg := config.GetDefaultLocal() + cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses + cfg.EnableGossipService = false // disable gossip service by default + + relayCfg := cfg + relayCfg.NetAddress = "127.0.0.1:0" + + netA, err := NewP2PNetwork(log.With("net", "netA"), relayCfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) + require.NoError(t, err) + netA.Start() + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsA[0]) + multiAddrStr := addrsA[0].String() + phoneBookAddresses := []string{multiAddrStr} + + nodeCfg := cfg + nodeCfg.NetAddress = "" + + netB, err := NewP2PNetwork(log.With("net", "netB"), nodeCfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) + require.NoError(t, err) + netB.Start() + defer netB.Stop() + + require.Eventually(t, func() bool { + return len(netA.service.Conns()) > 0 && len(netB.service.Conns()) > 0 + }, 1*time.Second, 50*time.Millisecond) + + require.False(t, netA.hasPeers()) + require.False(t, netB.hasPeers()) +} From 8eca278ffef1eee17a89e2d3d49ad9373e10bbac Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:26:20 -0400 Subject: [PATCH 191/218] tests: debug logging for TestVotersReloadFromDiskAfterOneStateProofCommitted (#6088) --- ledger/acctupdates_test.go | 14 +++++++++++++- ledger/blockqueue_test.go | 1 + ledger/ledger.go | 7 +++++-- ledger/ledger_test.go | 1 + ledger/tracker.go | 9 +++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 283fbcdc2e..a27a2be795 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -130,7 +130,18 @@ func makeMockLedgerForTrackerWithLogger(t testing.TB, inMemory bool, initialBloc Totals: totals, } } - return &mockLedgerForTracker{dbs: dbs, log: l, filename: fileName, inMemory: inMemory, blocks: blocks, deltas: deltas, consensusParams: config.Consensus[consensusVersion], consensusVersion: consensusVersion, accts: accts[0]} + ml := &mockLedgerForTracker{ + dbs: dbs, + log: l, + filename: fileName, + inMemory: inMemory, + blocks: blocks, + deltas: deltas, consensusParams: config.Consensus[consensusVersion], + consensusVersion: consensusVersion, + accts: accts[0], + trackers: trackerRegistry{log: l}, + } + return ml } @@ -160,6 +171,7 @@ func (ml *mockLedgerForTracker) fork(t testing.TB) *mockLedgerForTracker { filename: fn, consensusParams: ml.consensusParams, consensusVersion: ml.consensusVersion, + trackers: trackerRegistry{log: dblogger}, } for k, v := range ml.accts { newLedgerTracker.accts[k] = v diff --git a/ledger/blockqueue_test.go b/ledger/blockqueue_test.go index e74fbc0b3b..e72523be71 100644 --- a/ledger/blockqueue_test.go +++ b/ledger/blockqueue_test.go @@ -207,6 +207,7 @@ func TestBlockQueueSyncerDeletion(t *testing.T) { l := &Ledger{ log: log, blockDBs: blockDBs, + trackers: trackerRegistry{log: log}, } if test.tracker != nil { l.trackers.trackers = append(l.trackers.trackers, test.tracker) diff --git a/ledger/ledger.go b/ledger/ledger.go index 7459c23037..2f10724fee 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -214,8 +214,11 @@ func (l *Ledger) reloadLedger() error { blockListeners := make([]ledgercore.BlockListener, 0, len(l.notifier.listeners)) blockListeners = append(blockListeners, l.notifier.listeners...) - // close the trackers. - l.trackers.close() + // close the trackers if the registry was already initialized: opening a new ledger calls reloadLedger + // and there is nothing to close. Registry's logger is not initialized yet so close cannot log. + if l.trackers.trackers != nil { + l.trackers.close() + } // init block queue var err error diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index c97040f42c..f1ee8898c9 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -3415,6 +3415,7 @@ func TestLedgerRetainMinOffCatchpointInterval(t *testing.T) { l := &Ledger{} l.cfg = cfg l.archival = cfg.Archival + l.trackers.log = logging.TestingLog(t) for i := 1; i <= blocksToMake; i++ { minBlockToKeep := l.notifyCommit(basics.Round(i)) diff --git a/ledger/tracker.go b/ledger/tracker.go index 97098a572f..96e42e949f 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -467,6 +467,7 @@ func (tr *trackerRegistry) scheduleCommit(blockqRound, maxLookback basics.Round) // Dropping this dcc allows the blockqueue syncer to continue persisting other blocks // and ledger reads to proceed without being blocked by trackerMu lock. tr.accountsWriting.Done() + tr.log.Debugf("trackerRegistry.scheduleCommit: deferredCommits channel is full, skipping commit for (%d-%d)", dcc.oldBase, dcc.oldBase+basics.Round(dcc.offset)) } } } @@ -491,22 +492,27 @@ func (tr *trackerRegistry) isBehindCommittingDeltas(latest basics.Round) bool { } func (tr *trackerRegistry) close() { + tr.log.Debugf("trackerRegistry is closing") if tr.ctxCancel != nil { tr.ctxCancel() } // close() is called from reloadLedger() when and trackerRegistry is not initialized yet if tr.commitSyncerClosed != nil { + tr.log.Debugf("trackerRegistry is waiting for accounts writing to complete") tr.waitAccountsWriting() // this would block until the commitSyncerClosed channel get closed. <-tr.commitSyncerClosed + tr.log.Debugf("trackerRegistry done waiting for accounts writing") } + tr.log.Debugf("trackerRegistry is closing trackers") for _, lt := range tr.trackers { lt.close() } tr.trackers = nil tr.accts = nil + tr.log.Debugf("trackerRegistry has closed") } // commitSyncer is the syncer go-routine function which perform the database updates. Internally, it dequeues deferredCommits and @@ -525,11 +531,13 @@ func (tr *trackerRegistry) commitSyncer(deferredCommits chan *deferredCommitCont } case <-tr.ctx.Done(): // drain the pending commits queue: + tr.log.Debugf("commitSyncer is closing, draining the pending commits queue") drained := false for !drained { select { case <-deferredCommits: tr.accountsWriting.Done() + tr.log.Debugf("commitSyncer drained a pending commit") default: drained = true } @@ -648,6 +656,7 @@ func (tr *trackerRegistry) commitRound(dcc *deferredCommitContext) error { lt.postCommitUnlocked(tr.ctx, dcc) } + tr.log.Debugf("commitRound completed for (%d-%d)", dbRound, dbRound+basics.Round(offset)) return nil } From 05a52e26685f7dd94fd7ec852cb08589aed8b8d5 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:29:19 -0400 Subject: [PATCH 192/218] tests: fix flushing in TestVotersReloadFromDiskAfterOneStateProofCommitted (#6090) --- ledger/ledger_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index f1ee8898c9..c4bb74fcb5 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -2952,6 +2952,19 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi <-l.trackers.commitSyncerClosed l.trackers.commitSyncerClosed = nil + // it is possible a commmit was scheduled while commitSyncer was closing so that there is one pending task + // that required to be done before before the ledger can be closed, so drain the queue +outer: + for { + select { + case <-l.trackers.deferredCommits: + log.Info("drained deferred commit") + l.trackers.accountsWriting.Done() + default: + break outer + } + } + // flush one final time triggerTrackerFlush(t, l) From 94f1355bdabd34744bb30942fa9158d09ff742b4 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:46:23 -0400 Subject: [PATCH 193/218] netgoal: allow unknown template tokens (#6091) --- cmd/netgoal/network.go | 6 ++++-- netdeploy/remote/deployedNetwork.go | 20 ++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/cmd/netgoal/network.go b/cmd/netgoal/network.go index d4ed3c277c..fef3850868 100644 --- a/cmd/netgoal/network.go +++ b/cmd/netgoal/network.go @@ -35,6 +35,7 @@ var networkRecipeFile string var networkName string var networkGenesisVersionModifier string var miscStringStringTokens []string +var ignoreUnknownTokens bool var cpuprofilePath string @@ -56,7 +57,8 @@ func init() { networkBuildCmd.Flags().BoolVarP(&networkUseGenesisFiles, "use-existing-files", "e", false, "Use existing genesis files.") networkBuildCmd.Flags().BoolVarP(&bootstrapLoadingFile, "gen-db-files", "b", false, "Generate database files.") networkBuildCmd.Flags().BoolVarP(&networkIgnoreExistingDir, "force", "f", false, "Force generation into existing directory.") - networkBuildCmd.Flags().StringSliceVarP(&miscStringStringTokens, "val", "v", nil, "name=value, may be reapeated") + networkBuildCmd.Flags().StringSliceVarP(&miscStringStringTokens, "val", "v", nil, "name=value, may be repeated") + networkBuildCmd.Flags().BoolVarP(&ignoreUnknownTokens, "ignore", "i", false, "Ignore unknown tokens in network template file") networkBuildCmd.Flags().StringVar(&cpuprofilePath, "cpuprofile", "", "write cpu profile to path") rootCmd.PersistentFlags().StringVarP(&networkGenesisVersionModifier, "modifier", "m", "", "Override Genesis Version Modifier (eg 'v1')") @@ -136,7 +138,7 @@ func runBuildNetwork() error { return fmt.Errorf("error resolving network template file '%s' to full path: %v", networkTemplateFile, err) } - netCfg, err := remote.InitDeployedNetworkConfig(networkTemplateFile, buildConfig) + netCfg, err := remote.InitDeployedNetworkConfig(networkTemplateFile, buildConfig, ignoreUnknownTokens) if err != nil { return fmt.Errorf("error loading Network Config file '%s': %v", networkTemplateFile, err) } diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go index ce72071ff0..26f25a0da6 100644 --- a/netdeploy/remote/deployedNetwork.go +++ b/netdeploy/remote/deployedNetwork.go @@ -19,6 +19,7 @@ package remote import ( "encoding/binary" "encoding/json" + "errors" "fmt" "io/fs" "math/rand" @@ -58,14 +59,14 @@ var ErrDeployedNetworkInsufficientHosts = fmt.Errorf("target network requires mo // ErrDeployedNetworkNameCantIncludeWildcard is returned by Validate if network name contains '*' var ErrDeployedNetworkNameCantIncludeWildcard = fmt.Errorf("network name cannont include wild-cards") -// ErrDeployedNetworkTemplate A template file contained {{Field}} sections that were not handled by a corresponding Field value in configuration. -type ErrDeployedNetworkTemplate struct { - UnhandledTemplate string +// deployedNetworkTemplateError A template file contained {{Field}} sections that were not handled by a corresponding Field value in configuration. +type deployedNetworkTemplateError struct { + unhandledTemplate string } // Error satisfies error interface -func (ednt ErrDeployedNetworkTemplate) Error() string { - return fmt.Sprintf("config file contains unrecognized token: %s", ednt.UnhandledTemplate) +func (dnte deployedNetworkTemplateError) Error() string { + return fmt.Sprintf("config file contains unrecognized token: %s", dnte.unhandledTemplate) } // DeployedNetworkConfig represents the complete configuration specification for a deployed network @@ -123,10 +124,13 @@ int 1 ` // InitDeployedNetworkConfig loads the DeployedNetworkConfig from a file -func InitDeployedNetworkConfig(file string, buildConfig BuildConfig) (cfg DeployedNetworkConfig, err error) { +func InitDeployedNetworkConfig(file string, buildConfig BuildConfig, ignoreUnkTokens bool) (cfg DeployedNetworkConfig, err error) { processedFile, err := loadAndProcessConfig(file, buildConfig) if err != nil { - return + var dnte deployedNetworkTemplateError + if !errors.As(err, &dnte) || !ignoreUnkTokens { + return + } } err = json.Unmarshal([]byte(processedFile), &cfg) @@ -178,7 +182,7 @@ func replaceTokens(original string, buildConfig BuildConfig) (expanded string, e if closeIndex < 0 { closeIndex = len(expanded) - 2 } - return "", ErrDeployedNetworkTemplate{expanded[openIndex : closeIndex+2]} + return expanded, deployedNetworkTemplateError{expanded[openIndex : closeIndex+2]} } return From d2c4ca75c267e56a3b3d47a4cd97e4a75eb1fa7b Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:01:44 -0400 Subject: [PATCH 194/218] cmd: Add goal node subcommand to generate peer private key (#6078) --- cmd/goal/node.go | 1 + cmd/goal/p2pid.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 cmd/goal/p2pid.go diff --git a/cmd/goal/node.go b/cmd/goal/node.go index 2db08fd4e5..8bb103115b 100644 --- a/cmd/goal/node.go +++ b/cmd/goal/node.go @@ -79,6 +79,7 @@ func init() { nodeCmd.AddCommand(catchupCmd) // Once the server-side implementation of the shutdown command is ready, we should enable this one. //nodeCmd.AddCommand(shutdownCmd) + nodeCmd.AddCommand(p2pID) startCmd.Flags().StringVarP(&peerDial, "peer", "p", "", "Peer address to dial for initial connection") startCmd.Flags().StringVarP(&listenIP, "listen", "l", "", "Endpoint / REST address to listen on") diff --git a/cmd/goal/p2pid.go b/cmd/goal/p2pid.go new file mode 100644 index 0000000000..b35a946432 --- /dev/null +++ b/cmd/goal/p2pid.go @@ -0,0 +1,72 @@ +// Copyright (C) 2019-2024 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 . + +// generate a new p2p private key and print out peerID to stdout + +package main + +import ( + "fmt" + "os" + "path" + + "github.com/algorand/go-algorand/cmd/util/datadir" + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/network/p2p" + "github.com/algorand/go-algorand/util" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/spf13/cobra" +) + +var p2pID = &cobra.Command{ + Use: "generate-p2pid", + Short: "Generate a new p2p private key", + Long: "Generate a new p2p private key (saved to " + p2p.DefaultPrivKeyPath + ") and print out peerID to stdout", + Args: validateNoPosArgsFn, + Run: func(cmd *cobra.Command, args []string) { + anyError := false + datadir.OnDataDirs(func(dataDir string) { + exist := false + privKeyPath := path.Join(dataDir, p2p.DefaultPrivKeyPath) + if util.FileExists(privKeyPath) { + exist = true + } + + peerKey, err := p2p.GetPrivKey(config.Local{P2PPersistPeerID: true}, dataDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Error obtaining private key: %v\n", err) + anyError = true + return + } + peerID, err := peer.IDFromPublicKey(peerKey.GetPublic()) + if err != nil { + fmt.Fprintf(os.Stderr, "Error obtaining peerID from a key: %v\n", err) + anyError = true + return + } + + fmt.Printf("PeerID: %s\n", peerID.String()) + if !exist { + fmt.Printf("Private key saved to %s\n", privKeyPath) + } else { + fmt.Printf("Used existing key %s\n", privKeyPath) + } + }) + if anyError { + os.Exit(1) + } + }, +} From a0815c1435d522a880f0c28210df323e7d611bff Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:33:40 -0400 Subject: [PATCH 195/218] tests: fixes to e2e test logs parsers (#6093) --- test/heapwatch/agreement-log.py | 22 ++- test/heapwatch/topology-extract-ws-e2e.py | 181 ++++++++++++++++++++++ test/heapwatch/topology-extract-ws.py | 6 +- 3 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 test/heapwatch/topology-extract-ws-e2e.py diff --git a/test/heapwatch/agreement-log.py b/test/heapwatch/agreement-log.py index 4109b37a71..873374806d 100644 --- a/test/heapwatch/agreement-log.py +++ b/test/heapwatch/agreement-log.py @@ -12,6 +12,7 @@ import json import logging import os +import re import time from termcolor import COLORS, colored @@ -80,6 +81,14 @@ def process_json_line(line: str, node_name: str, by_node: dict, events: list): return result return None +def node_name_from_line(line: str): + """Extracts node name from the line like "libgoalFixture.go:376: Relay0/node.log:""" + pattern = r'([^:]+?)/node\.log' + match = re.search(pattern, line) + if match: + return match.group(1).strip() + return None + def main(): os.environ['TZ'] = 'UTC' time.tzset() @@ -134,16 +143,21 @@ def main(): libgoalFixture.go:374: ===================... libgoalFixture.go:376: Relay0/node.log: libgoalFixture.go:379: {"file":"server.go"... + + OR without libgoalFixture prefix (depends on the test) + ================================= + Relay0/node.log: + {"file":"server.go","function":"gi... """ node_name = None if line0.endswith('node.log:'): - node_name = line0.split(' ')[1].split('/')[0] - logger.info('found node name: %s', node_name) + node_name = node_name_from_line(line0) + logger.info('found node name: \'%s\'', node_name) for line in file: line = line.strip() if line.endswith('node.log:'): - node_name = line.split(' ')[1].split('/')[0] - logger.info('found node name: %s', node_name) + node_name = node_name_from_line(line) + logger.info('found node name: \'%s\'', node_name) if node_name: for line in file: json_start = line.find('{') diff --git a/test/heapwatch/topology-extract-ws-e2e.py b/test/heapwatch/topology-extract-ws-e2e.py new file mode 100644 index 0000000000..63e3bf8a6c --- /dev/null +++ b/test/heapwatch/topology-extract-ws-e2e.py @@ -0,0 +1,181 @@ +""" +WSNet network topology extraction script from e2e test output single log file. + +1. Save the e2e test output to a file +It starts with a line like this: +libgoalFixture.go:374: ===================... +libgoalFixture.go:376: Relay0/node.log: +libgoalFixture.go:379: {"file":"server.go"... + +OR like this: +================================= +Relay0/node.log: +{"file":"server.go","function":"gi... + +2. Run this script `python3 topology-extract-ws-e2e.py -o top.json e2e-test.log +3. Run the visualizer `topology-viz.py top.json` +""" +import argparse +from datetime import datetime +import json +import logging +import re +import sys +from typing import Dict, List + +logger = logging.getLogger(__name__) + + +def node_name_from_line(line: str): + """Extracts node name from the line like "libgoalFixture.go:376: Relay0/node.log:""" + pattern = r'([^:]+?)/node\.log' + match = re.search(pattern, line) + if match: + return match.group(1).strip() + return None + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('log_file', help='log file path') + ap.add_argument('-o', '--output', type=argparse.FileType('wt', encoding='utf-8'), help=f'save topology to the file specified instead of showing it') + ap.add_argument('-t', '--timestamp', action='store_true', help=f'store connection timestamp for each edge') + args = ap.parse_args() + + log_file = args.log_file + + nodes: List[str] = [] + edges: List[List[str]] = [] + mapping: Dict[str, str] = {} + + addr_to_name = {} + with open(log_file, 'rt', encoding='utf-8') as file: + line0 = None + while not line0: + line0 = file.readline() + line0 = line0.strip() + + node_name = None + if line0.endswith('node.log:'): + node_name = node_name_from_line(line0) + logger.info('found node name: \'%s\'', node_name) + for line in file: + line = line.strip() + if line.endswith('node.log:'): + node_name = node_name_from_line(line) + logger.info('found node name: \'%s\'', node_name) + if node_name: + nodes.append(node_name) + for line in file: + json_start = line.find('{') + if json_start == -1: + # end of continuous json block + node_name = None + break + line = line[json_start:] + + if "serving genesisID" in line: + data = json.loads(line.strip()) + match = re.search(r'(?:http://)?(\d+\.\d+\.\d+\.\d+:\d+)', data['msg']) + if match: + addr = match.group(1) + addr_to_name[addr] = node_name + + # Check if line contains relevant substrings before parsing as JSON + if "Accepted incoming connection from peer" in line or "Made outgoing connection to peer" in line: + data = json.loads(line.strip()) + + # Check for incoming connections + if "Accepted incoming connection from peer" in data.get("msg", ""): + remote = data['remote'] + match = re.search(r'(?:http://)?(\d+\.\d+\.\d+\.\d+:\d+)', remote) + remote_addr = match.group(1) + remote_name = remote_addr + if resolved := addr_to_name.get(remote_addr): + remote_name = resolved + source = remote_name + target = node_name + if args.timestamp: + # datetime is not serializable, so we store it as string for now + edge = (source, target, {'dt': data["time"]}) + else: + edge = (source, target) + + # Check for outgoing connections + elif "Made outgoing connection to peer" in data.get('msg', ""): + remote_addr = data['remote'] + remote_name = remote_addr + if resolved := addr_to_name.get(remote_addr): + remote_name = resolved + target = remote_name + source = node_name + + if args.timestamp: + # datetime is not serializable, so we store it as string for now + edge = (source, target, {'dt': data["time"]}) + else: + edge = (source, target) + + edges.append(edge) + + # apply names that were learned from the logs + for i, edge in enumerate(edges): + e0 = edge[0] + e0 = addr_to_name.get(e0, e0) + e1 = edge[1] + e1 = addr_to_name.get(e1, e1) + if len(edge) == 3: + edge = (e0, e1, edge[2]) + else: + edge = (e0, e1) + edges[i] = edge + + orig_nodes = set(nodes) + # remap non-canonical names (like poorNode) and non-resolved ip addresses to some nodes + for i, node in enumerate(nodes): + if not node.startswith(('N', 'R', 'NPN')): + nodes[i] = 'N-' + node + + # remove non-resolved ip addresses from edges - most likely these N, NPN already counted + # because both nodes and relays logs are processed + trimmed_edges = [] + for i, edge in enumerate(edges): + e0 = edge[0] + e1 = edge[1] + if e0 not in orig_nodes or e1 not in orig_nodes: + # some non-resolved ip address, skip + continue + + if not e0.startswith(('N', 'R', 'NPN')): + e0 = 'N-' + e0 + if not e1.startswith(('N', 'R', 'NPN')): + e1 = 'N-' + e1 + + if len(edge) == 3: + edge = (e0, e1, edge[2]) + else: + edge = (e0, e1) + trimmed_edges.append(edge) + + result = { + "mapping": mapping, + "nodes": nodes, + "edges": trimmed_edges + } + + if args.timestamp and not args.output: + edges = sorted(edges, key=lambda x: x[2]['dt']) + for edge in edges: + ts = datetime.strptime(edge[2]['dt'], "%Y-%m-%dT%H:%M:%S.%f%z") + print('%15s %5s -> %-5s' % (ts.strftime('%H:%M:%S.%f'), edge[0], edge[1])) + return + + if args.output: + json.dump(result, args.output, indent=2) + else: + json.dump(result, sys.stdout, indent=2) + print(file=sys.stdout) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + main() diff --git a/test/heapwatch/topology-extract-ws.py b/test/heapwatch/topology-extract-ws.py index 75f1d99f57..0b0765b007 100644 --- a/test/heapwatch/topology-extract-ws.py +++ b/test/heapwatch/topology-extract-ws.py @@ -68,7 +68,11 @@ def main(): remote_name = ip_to_name[remote_ip] source = remote_name target = mapped - edges.append((source, target)) + if args.timestamp: + # datetime is not serializable, so we store it as string for now + edge = (source, target, {'dt': data["time"]}) + else: + edge = (source, target) # Check for outgoing connections elif "Made outgoing connection to peer" in data.get('msg', ""): From 9924574bd7c48e559e5ffd2060df11801a7920ea Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:11:33 -0400 Subject: [PATCH 196/218] tests: fix e2e subs goal-partkey-commands (#6095) --- test/scripts/e2e.sh | 11 +++- .../scripts/e2e_subs/goal-partkey-commands.sh | 56 +++++++++++++++---- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/test/scripts/e2e.sh b/test/scripts/e2e.sh index a3b2c756fa..6f6e4a1f03 100755 --- a/test/scripts/e2e.sh +++ b/test/scripts/e2e.sh @@ -181,7 +181,16 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then clientrunner="${TEMPDIR}/ve/bin/python3 e2e_client_runner.py ${RUN_KMD_WITH_UNSAFE_SCRYPT}" - $clientrunner ${KEEP_TEMPS_CMD_STR} "$SRCROOT"/test/scripts/e2e_subs/*.{sh,py} + if [ -n "$TESTFILTER" ]; then + echo "Running test: $TESTFILTER" + $clientrunner ${KEEP_TEMPS_CMD_STR} "$SRCROOT"/test/scripts/e2e_subs/${TESTFILTER} + echo -n "deactivating..." + deactivate + echo "done" + exit + else + $clientrunner ${KEEP_TEMPS_CMD_STR} "$SRCROOT"/test/scripts/e2e_subs/*.{sh,py} + fi # If the temporary artifact directory exists, then the test artifact needs to be created if [ -d "${TEMPDIR}/net" ]; then diff --git a/test/scripts/e2e_subs/goal-partkey-commands.sh b/test/scripts/e2e_subs/goal-partkey-commands.sh index 94c831c86e..dd60d44016 100755 --- a/test/scripts/e2e_subs/goal-partkey-commands.sh +++ b/test/scripts/e2e_subs/goal-partkey-commands.sh @@ -6,9 +6,14 @@ set -x date "+$0 start %Y%m%d_%H%M%S" +WALLET=$1 + +gcmd="goal -w ${WALLET}" +INITIAL_ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') + # Registered Account ParticipationID Last Used First round Last round # yes LFMT...RHJQ 4UPT6AQC... 4 0 3000000 -OUTPUT=$(goal account listpartkeys) +OUTPUT=$(${gcmd} account listpartkeys) # In case there are multiple keys, make sure we are checking the correct one. OUTPUT=$(echo "$OUTPUT"|grep "yes.*3000"|tr -s ' ') if [[ "$OUTPUT" != yes* ]]; then echo "Registered should be 'yes' but wasn't."; exit 1; fi @@ -29,7 +34,7 @@ if [[ $(echo "$OUTPUT" | cut -d' ' -f 6) != 3000 ]]; then echo "Last round sh #Key dilution: 10000 #Selection key: esIsBJB86P+sLeqO3gVoLBGfpuwYlWN4lNzz2AYslTo= #Voting key: W1OcXLZsaATyOd5FbhRgXHmcywvn++xEVUAQ0NejmW4= -OUTPUT=$(goal account partkeyinfo) +OUTPUT=$(${gcmd} account partkeyinfo) if ! echo "$OUTPUT" | grep -q 'First round:[[:space:]]* 0'; then echo "First round should have been 0."; exit 1; fi if ! echo "$OUTPUT" | grep -q 'Last round:[[:space:]]* 3000'; then echo "Last round should have been 3000."; exit 1; fi if ! echo "$OUTPUT" | grep -q 'Effective last round:[[:space:]]* 3000'; then echo "Effective last round should have been 3000."; exit 1; fi @@ -39,18 +44,31 @@ if ! echo "$OUTPUT" | grep -q 'Participation ID:[[:space:]]*[[:alnum:]]\{52\}'; # Test multiple data directory supported NUM_OUTPUT_1=$(echo "$OUTPUT"|grep -c 'Participation ID') -OUTPUT=$(goal account partkeyinfo -d "$ALGORAND_DATA" -d "$ALGORAND_DATA2") +OUTPUT=$(${gcmd} account partkeyinfo -d "$ALGORAND_DATA" -d "$ALGORAND_DATA2") NUM_OUTPUT_2=$(echo "$OUTPUT"|grep -c 'Participation ID') if (( "$NUM_OUTPUT_2" <= "$NUM_OUTPUT_1" )); then echo "Should have found more participation keys when checking both data directories."; exit 1; fi # get stderr from this one -OUTPUT=$(goal account listpartkeys -d "$ALGORAND_DATA" -d "$ALGORAND_DATA2" 2>&1) +OUTPUT=$(${gcmd} account listpartkeys -d "$ALGORAND_DATA" -d "$ALGORAND_DATA2" 2>&1) EXPECTED_ERR="Only one data directory can be specified for this command." if [[ "$OUTPUT" != "$EXPECTED_ERR" ]]; then echo -e "Unexpected output from multiple data directories with 'listpartkeys': \n$OUTPUT"; exit 1; fi +# done with manual error checking +set -exo pipefail + +fail_test () { + echo "test_fail: $1" + exit 1 +} + create_and_fund_account () { + set +x # disable command echoing to hide the account funding output local TEMP_ACCT=$(${gcmd} account new|awk '{ print $6 }') - ${gcmd} clerk send -f "$INITIAL_ACCOUNT" -t "$TEMP_ACCT" -a 1000000 > /dev/null + SEND_OUTOUT=$(${gcmd} clerk send -f "$INITIAL_ACCOUNT" -t "$TEMP_ACCT" -a 1000000 2>&1) + if [[ $SEND_OUTOUT == *"Couldn't broadcast tx"* ]]; then + fail_test "Failed to fund account: $SEND_OUTOUT" + fi + set -x echo "$TEMP_ACCT" } @@ -60,14 +78,16 @@ create_and_fund_account () { # $3 - error message verify_registered_state () { # look for participation ID anywhere in the partkeyinfo output - if ! goal account partkeyinfo | grep -q "$2"; then - fail_test "Key was not installed properly: $3" + PARTKEY_OUTPUT=$(${gcmd} account partkeyinfo) + if ! echo "$PARTKEY_OUTPUT" | grep -q "$2"; then + fail_test "Key $2 was not installed properly for cmd '$3':\n$PARTKEY_OUTPUT" fi # looking for yes/no, and the 8 character head of participation id in this line: # yes LFMT...RHJQ 4UPT6AQC... 4 0 3000 - if ! goal account listpartkeys | grep -q "$1.*$(echo "$2" | cut -c1-8)\.\.\."; then - fail_test "Unexpected key state: $3" + LISTKEY_OUTPUT=$(${gcmd} account listpartkeys) + if ! echo "$LISTKEY_OUTPUT" | grep -q "$1.*$(echo "$2" | cut -c1-8)"; then + fail_test "Unexpected key $2 state ($1) for cmd '$3':\n$LISTKEY_OUTPUT" fi } @@ -75,19 +95,31 @@ verify_registered_state () { # install manually generated participation keys (do not register) NEW_ACCOUNT_1=$(create_and_fund_account) algokey part generate --keyfile test_partkey --first 0 --last 3000 --parent "$NEW_ACCOUNT_1" -PARTICIPATION_ID_1=$(goal account installpartkey --delete-input --partkey test_partkey|awk '{ print $7 }') +OUTPUT=$(${gcmd} account installpartkey --delete-input --partkey test_partkey) +PARTICIPATION_ID_1=$(echo "$OUTPUT" |awk '{ print $7 }') verify_registered_state "no" "$PARTICIPATION_ID_1" "goal account installpartkey" # goal account addpartkey # generate and install participation keys (do not register) +# ============= Example output ============= +# Please stand by while generating keys. This might take a few minutes... +# Participation key generation successful +# Transaction id for status change transaction: U3SWNVPUODOUHHUM3W3QP3DTQPO6GLYVOB2A6UYBKAWKJP5T4GEQ +# Transaction U3SWNVPUODOUHHUM3W3QP3DTQPO6GLYVOB2A6UYBKAWKJP5T4GEQ still pending as of round 11 +# Transaction U3SWNVPUODOUHHUM3W3QP3DTQPO6GLYVOB2A6UYBKAWKJP5T4GEQ still pending as of round 12 +# Transaction U3SWNVPUODOUHHUM3W3QP3DTQPO6GLYVOB2A6UYBKAWKJP5T4GEQ committed in round 13 +# Participation key installed successfully, Participation ID: NTJG7MGXZ5SCLIJXW7T2VZGLTVY47QBFTQSOC7JMIF7LWXVMQOPQ +# Generated with goal v3.26.226519 NEW_ACCOUNT_2=$(create_and_fund_account) -PARTICIPATION_ID_2=$(goal account addpartkey -a "$NEW_ACCOUNT_2" --roundFirstValid 0 --roundLastValid 3000|awk '{ print $7 }') +OUTPUT=$(${gcmd} account addpartkey -a "$NEW_ACCOUNT_2" --roundFirstValid 0 --roundLastValid 3000) +PARTICIPATION_ID_2=$(echo "$OUTPUT" | grep "Participation ID" | awk '{ print $7 }') verify_registered_state "no" "$PARTICIPATION_ID_2" "goal account addpartkey" # goal account renewpartkeys # generate, install, and register NEW_ACCOUNT_3=$(create_and_fund_account) -PARTICIPATION_ID_3=$(${gcmd} account renewpartkey --roundLastValid 3000 -a "$NEW_ACCOUNT_3"|tail -n 1|awk '{ print $7 }') +OUTPUT=$(${gcmd} account renewpartkey --roundLastValid 3000 -a "$NEW_ACCOUNT_3") +PARTICIPATION_ID_3=$(echo "$OUTPUT" | grep "Participation ID" | awk '{ print $7 }') verify_registered_state "yes" "$PARTICIPATION_ID_3" "goal account renewpartkey" # goal account changeonlinstatus (--account) From c6a433b5035377246340710a91b295553bb6ac1f Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:52:38 -0400 Subject: [PATCH 197/218] p2p: do not advertise private and non-routable addresses (#6092) --- network/hybridNetwork_test.go | 2 +- network/p2p/p2p.go | 94 +++++++++++++++++++- network/p2p/p2p_test.go | 160 ++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 2 deletions(-) diff --git a/network/hybridNetwork_test.go b/network/hybridNetwork_test.go index 7c76c1e38e..842bb10b15 100644 --- a/network/hybridNetwork_test.go +++ b/network/hybridNetwork_test.go @@ -64,7 +64,7 @@ func TestHybridNetwork_DuplicateConn(t *testing.T) { // make it net address and restart the node relayCfg.NetAddress = addr relayCfg.PublicAddress = addr - relayCfg.P2PNetAddress = ":0" + relayCfg.P2PNetAddress = "127.0.0.1:0" netA, err = NewHybridP2PNetwork(log.With("node", "netA"), relayCfg, p2pKeyDir, nil, genesisID, "net", &nopeNodeInfo{}) require.NoError(t, err) diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 21782dce44..e908f148d8 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -20,6 +20,7 @@ import ( "context" "encoding/base32" "fmt" + "net" "runtime" "strings" "time" @@ -43,6 +44,7 @@ import ( "github.com/libp2p/go-libp2p/p2p/security/noise" "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" ) // SubNextCancellable is an abstraction for pubsub.Subscription @@ -108,18 +110,35 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. ua := fmt.Sprintf("algod/%d.%d (%s; commit=%s; %d) %s(%s)", version.Major, version.Minor, version.Channel, version.CommitHash, version.BuildNumber, runtime.GOOS, runtime.GOARCH) var listenAddr string + var needAddressFilter bool if cfg.NetAddress != "" { if parsedListenAddr, perr := netAddressToListenAddress(cfg.NetAddress); perr == nil { listenAddr = parsedListenAddr + + // check if the listen address is a specific address or a "all interfaces" address (0.0.0.0 or ::) + // in this case enable the address filter. + // this also means the address filter is not enabled for NetAddress set to + // a specific address including loopback and private addresses. + if manet.IsIPUnspecified(multiaddr.StringCast(listenAddr)) { + needAddressFilter = true + } + } else { + logging.Base().Warnf("failed to parse NetAddress %s: %v", cfg.NetAddress, perr) } } else { - // don't listen if NetAddress is not set. + logging.Base().Debug("p2p NetAddress is not set, not listening") listenAddr = "" } var enableMetrics = func(cfg *libp2p.Config) error { cfg.DisableMetrics = false; return nil } metrics.DefaultRegistry().Register(&metrics.PrometheusDefaultMetrics) + var addrFactory func(addrs []multiaddr.Multiaddr) []multiaddr.Multiaddr + if needAddressFilter { + logging.Base().Debug("private addresses filter is enabled") + addrFactory = addressFilter + } + rm, err := configureResourceManager(cfg) if err != nil { return nil, "", err @@ -135,6 +154,7 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. libp2p.Security(noise.ID, noise.New), enableMetrics, libp2p.ResourceManager(rm), + libp2p.AddrsFactory(addrFactory), ) return host, listenAddr, err } @@ -321,3 +341,75 @@ func formatPeerTelemetryInfoProtocolName(telemetryID string, telemetryInstance s base32.StdEncoding.EncodeToString([]byte(telemetryInstance)), ) } + +var private6 = parseCIDR([]string{ + "100::/64", + "2001:2::/48", + "2001:db8::/32", // multiaddr v0.13 has it +}) + +// parseCIDR converts string CIDRs to net.IPNet. +// function panics on errors so that it is only called during initialization. +func parseCIDR(cidrs []string) []*net.IPNet { + result := make([]*net.IPNet, 0, len(cidrs)) + var ipnet *net.IPNet + var err error + for _, cidr := range cidrs { + if _, ipnet, err = net.ParseCIDR(cidr); err != nil { + panic(err) + } + result = append(result, ipnet) + } + return result +} + +// addressFilter filters out private and unroutable addresses +func addressFilter(addrs []multiaddr.Multiaddr) []multiaddr.Multiaddr { + if logging.Base().IsLevelEnabled(logging.Debug) { + var b strings.Builder + for _, addr := range addrs { + b.WriteRune(' ') + b.WriteString(addr.String()) + b.WriteRune(' ') + } + logging.Base().Debugf("addressFilter input: %s", b.String()) + } + + res := make([]multiaddr.Multiaddr, 0, len(addrs)) + for _, addr := range addrs { + if manet.IsPublicAddr(addr) { + if _, err := addr.ValueForProtocol(multiaddr.P_IP4); err == nil { + // no rules for IPv4 at the moment, accept + res = append(res, addr) + continue + } + + isPrivate := false + a, err := addr.ValueForProtocol(multiaddr.P_IP6) + if err != nil { + logging.Base().Warnf("failed to get IPv6 addr from %s: %v", addr, err) + continue + } + addrIP := net.ParseIP(a) + for _, ipnet := range private6 { + if ipnet.Contains(addrIP) { + isPrivate = true + break + } + } + if !isPrivate { + res = append(res, addr) + } + } + } + if logging.Base().IsLevelEnabled(logging.Debug) { + var b strings.Builder + for _, addr := range res { + b.WriteRune(' ') + b.WriteString(addr.String()) + b.WriteRune(' ') + } + logging.Base().Debugf("addressFilter output: %s", b.String()) + } + return res +} diff --git a/network/p2p/p2p_test.go b/network/p2p/p2p_test.go index dab6aa5456..2da5782afc 100644 --- a/network/p2p/p2p_test.go +++ b/network/p2p/p2p_test.go @@ -19,14 +19,19 @@ package p2p import ( "context" "fmt" + "net" "testing" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/network/p2p/peerstore" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -169,3 +174,158 @@ func TestP2PProtocolAsMeta(t *testing.T) { require.Equal(t, h1TID, tid) require.Equal(t, h1Inst, inst) } + +func TestP2PPrivateAddresses(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + privAddrList := []string{ + "/ip4/10.0.0.0/ipcidr/8", + "/ip4/100.64.0.0/ipcidr/10", + "/ip4/169.254.0.0/ipcidr/16", + "/ip4/172.16.0.0/ipcidr/12", + "/ip4/192.0.0.0/ipcidr/24", + "/ip4/192.0.2.0/ipcidr/24", + "/ip4/192.88.99.0/ipcidr/24", + "/ip4/192.168.0.0/ipcidr/16", + "/ip4/198.18.0.0/ipcidr/15", + "/ip4/198.51.100.0/ipcidr/24", + "/ip4/203.0.113.0/ipcidr/24", + "/ip4/224.0.0.0/ipcidr/4", + "/ip4/224.0.0.0/ipcidr/4", + "/ip4/233.252.0.0/ipcidr/4", + "/ip4/255.255.255.255/ipcidr/32", + "/ip6/fc00::/ipcidr/7", + "/ip6/fe80::/ipcidr/10", + } + + // these are handled by addrFilter explicitly as a custom filter + extra := []string{ + "/ip6/100::/ipcidr/64", + "/ip6/2001:2::/ipcidr/48", + "/ip6/2001:db8::/ipcidr/32", // multiaddr v0.13 has it + } + + for _, addr := range privAddrList { + ma := multiaddr.StringCast(addr) + require.False(t, manet.IsPublicAddr(ma), "public check failed on %s", addr) + require.Empty(t, addressFilter([]multiaddr.Multiaddr{ma}), "addrFilter failed on %s", addr) + } + + for _, addr := range extra { + ma := multiaddr.StringCast(addr) + require.Empty(t, addressFilter([]multiaddr.Multiaddr{ma}), "addrFilter failed on %s", addr) + } + + // ensure addrFilter allows normal addresses + valid := []string{ + "/ip4/3.4.5.6/tcp/1234", + "/ip6/200:11::/tcp/1234", + } + + for _, addr := range valid { + ma := multiaddr.StringCast(addr) + require.Equal(t, []multiaddr.Multiaddr{ma}, addressFilter([]multiaddr.Multiaddr{ma}), "addrFilter failed on %s", addr) + } +} + +func TestP2PMaNetIsIPUnspecified(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + unspecified := []string{ + ":0", + ":1234", + "0.0.0.0:2345", + "0.0.0.0:0", + } + for _, addr := range unspecified { + parsed, err := netAddressToListenAddress(addr) + require.NoError(t, err) + require.True(t, manet.IsIPUnspecified(multiaddr.StringCast(parsed)), "expected %s to be unspecified", addr) + } + + specified := []string{ + "127.0.0.1:0", + "127.0.0.1:1234", + "1.2.3.4:5678", + "1.2.3.4:0", + "192.168.0.111:0", + "10.0.0.1:101", + } + for _, addr := range specified { + parsed, err := netAddressToListenAddress(addr) + require.NoError(t, err) + require.False(t, manet.IsIPUnspecified(multiaddr.StringCast(parsed)), "expected %s to be specified", addr) + } + + // also make sure IsIPUnspecified supports IPv6 + unspecified6 := []string{ + "/ip6/::/tcp/1234", + } + for _, addr := range unspecified6 { + require.True(t, manet.IsIPUnspecified(multiaddr.StringCast(addr)), "expected %s to be unspecified", addr) + } +} + +// TestP2PMakeHostAddressFilter ensures that the host address filter is enabled only when the +// NetAddress is set to "all interfaces" value (0.0.0.0:P or :P) +func TestP2PMakeHostAddressFilter(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + td := t.TempDir() + pstore, err := peerstore.NewPeerStore(nil, "test") + require.NoError(t, err) + + // check "all interfaces" addr + for _, addr := range []string{":0", "0.0.0.0:0"} { + cfg := config.GetDefaultLocal() + cfg.NetAddress = addr + host, la, err := MakeHost(cfg, td, pstore) + require.NoError(t, err) + require.Equal(t, "/ip4/0.0.0.0/tcp/0", la) + require.Empty(t, host.Addrs()) + + mala, err := multiaddr.NewMultiaddr(la) + require.NoError(t, err) + host.Network().Listen(mala) + require.Empty(t, host.Addrs()) + host.Close() + } + + // check specific addresses IPv4 retrieved from the system + addresses := []string{} + ifaces, err := net.Interfaces() + require.NoError(t, err) + for _, i := range ifaces { + addrs, err := i.Addrs() + require.NoError(t, err) + for _, a := range addrs { + switch v := a.(type) { + case *net.IPAddr: + if v.IP.To4() != nil { + addresses = append(addresses, v.IP.String()) + } + case *net.IPNet: + if v.IP.To4() != nil { + addresses = append(addresses, v.IP.String()) + } + } + } + } + for _, addr := range addresses { + cfg := config.GetDefaultLocal() + cfg.NetAddress = addr + ":0" + host, la, err := MakeHost(cfg, td, pstore) + require.NoError(t, err) + require.Equal(t, "/ip4/"+addr+"/tcp/0", la) + require.Empty(t, host.Addrs()) + mala, err := multiaddr.NewMultiaddr(la) + require.NoError(t, err) + err = host.Network().Listen(mala) + require.NoError(t, err) + require.NotEmpty(t, host.Addrs()) + host.Close() + } +} From 602d9507643a42891d724682dcadc9630b7ef8e6 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:30:10 -0400 Subject: [PATCH 198/218] p2p: handle txns in pubsub validator (#6070) Co-authored-by: cce <51567+cce@users.noreply.github.com> Co-authored-by: Jason Paulos --- components/mocks/mockNetwork.go | 4 +- data/transactions/verify/txnBatch.go | 54 +++++-- data/transactions/verify/txnBatch_test.go | 48 +++++- data/txHandler.go | 160 +++++++++++------- data/txHandler_test.go | 178 +++++++++++++++------ network/gossipNode.go | 45 ++---- network/hybridNetwork.go | 8 +- network/multiplexer.go | 42 ++--- network/p2p/pubsub.go | 1 + network/p2p/pubsubTracer.go | 98 ++++++++++++ network/p2pNetwork.go | 20 +-- network/p2pNetwork_test.go | 54 +++---- network/wsNetwork.go | 6 +- test/e2e-go/features/p2p/p2p_basic_test.go | 79 ++++++++- util/metrics/metrics.go | 11 ++ 15 files changed, 558 insertions(+), 250 deletions(-) create mode 100644 network/p2p/pubsubTracer.go diff --git a/components/mocks/mockNetwork.go b/components/mocks/mockNetwork.go index 4f145b1841..3a1795f057 100644 --- a/components/mocks/mockNetwork.go +++ b/components/mocks/mockNetwork.go @@ -91,8 +91,8 @@ func (network *MockNetwork) RegisterHandlers(dispatch []network.TaggedMessageHan func (network *MockNetwork) ClearHandlers() { } -// RegisterProcessors - empty implementation. -func (network *MockNetwork) RegisterProcessors(dispatch []network.TaggedMessageProcessor) { +// RegisterValidatorHandlers - empty implementation. +func (network *MockNetwork) RegisterValidatorHandlers(dispatch []network.TaggedMessageValidatorHandler) { } // ClearProcessors - empty implementation diff --git a/data/transactions/verify/txnBatch.go b/data/transactions/verify/txnBatch.go index 8619208da8..e2bd95d4ea 100644 --- a/data/transactions/verify/txnBatch.go +++ b/data/transactions/verify/txnBatch.go @@ -17,7 +17,7 @@ package verify import ( - "errors" + "fmt" "sync/atomic" "github.com/algorand/go-algorand/crypto" @@ -98,10 +98,16 @@ func (bl *batchLoad) addLoad(txngrp []transactions.SignedTxn, gctx *GroupContext } +// TxnGroupBatchSigVerifier provides Verify method to synchronously verify a group of transactions +// It starts a new block listener to receive latests block headers for the sig verification +type TxnGroupBatchSigVerifier struct { + cache VerifiedTransactionCache + nbw *NewBlockWatcher + ledger logic.LedgerForSignature +} + type txnSigBatchProcessor struct { - cache VerifiedTransactionCache - nbw *NewBlockWatcher - ledger logic.LedgerForSignature + TxnGroupBatchSigVerifier resultChan chan<- *VerificationResult droppedChan chan<- *UnverifiedTxnSigJob } @@ -142,27 +148,49 @@ func (tbp txnSigBatchProcessor) sendResult(veTxnGroup []transactions.SignedTxn, } } -// MakeSigVerifyJobProcessor returns the object implementing the stream verifier Helper interface -func MakeSigVerifyJobProcessor(ledger LedgerForStreamVerifier, cache VerifiedTransactionCache, - resultChan chan<- *VerificationResult, droppedChan chan<- *UnverifiedTxnSigJob) (svp execpool.BatchProcessor, err error) { +// MakeSigVerifier creats a new TxnGroupBatchSigVerifier for synchronous verification of transactions +func MakeSigVerifier(ledger LedgerForStreamVerifier, cache VerifiedTransactionCache) (TxnGroupBatchSigVerifier, error) { latest := ledger.Latest() latestHdr, err := ledger.BlockHdr(latest) if err != nil { - return nil, errors.New("MakeStreamVerifier: Could not get header for previous block") + return TxnGroupBatchSigVerifier{}, fmt.Errorf("MakeSigVerifier: Could not get header for previous block: %w", err) } nbw := MakeNewBlockWatcher(latestHdr) ledger.RegisterBlockListeners([]ledgercore.BlockListener{nbw}) + verifier := TxnGroupBatchSigVerifier{ + cache: cache, + nbw: nbw, + ledger: ledger, + } + + return verifier, nil +} + +// MakeSigVerifyJobProcessor returns the object implementing the stream verifier Helper interface +func MakeSigVerifyJobProcessor( + ledger LedgerForStreamVerifier, cache VerifiedTransactionCache, + resultChan chan<- *VerificationResult, droppedChan chan<- *UnverifiedTxnSigJob, +) (svp execpool.BatchProcessor, err error) { + sigVerifier, err := MakeSigVerifier(ledger, cache) + if err != nil { + return nil, err + } return &txnSigBatchProcessor{ - cache: cache, - nbw: nbw, - ledger: ledger, - droppedChan: droppedChan, - resultChan: resultChan, + TxnGroupBatchSigVerifier: sigVerifier, + droppedChan: droppedChan, + resultChan: resultChan, }, nil } +// Verify synchronously verifies the signatures of the transactions in the group +func (sv *TxnGroupBatchSigVerifier) Verify(stxs []transactions.SignedTxn) error { + blockHeader := sv.nbw.getBlockHeader() + _, err := txnGroup(stxs, blockHeader, sv.cache, sv.ledger, nil) + return err +} + func (tbp *txnSigBatchProcessor) ProcessBatch(txns []execpool.InputJob) { batchVerifier, ctx := tbp.preProcessUnverifiedTxns(txns) failed, err := batchVerifier.VerifyWithFeedback() diff --git a/data/transactions/verify/txnBatch_test.go b/data/transactions/verify/txnBatch_test.go index 45693f58a1..27dcc56343 100644 --- a/data/transactions/verify/txnBatch_test.go +++ b/data/transactions/verify/txnBatch_test.go @@ -139,9 +139,7 @@ func verifyResults(txnGroups [][]transactions.SignedTxn, badTxnGroups map[uint64 require.GreaterOrEqual(t, len(unverifiedGroups), badSigResultCounter) for _, txn := range unverifiedGroups { u, _ := binary.Uvarint(txn[0].Txn.Note) - if _, has := badTxnGroups[u]; has { - delete(badTxnGroups, u) - } + delete(badTxnGroups, u) } require.Empty(t, badTxnGroups, "unverifiedGroups should have all the transactions with invalid sigs") } @@ -301,6 +299,7 @@ func TestGetNumberOfBatchableSigsInGroup(t *testing.T) { txnGroups[mod][0].Sig = crypto.Signature{} batchSigs, err := UnverifiedTxnSigJob{TxnGroup: txnGroups[mod]}.GetNumberOfBatchableItems() require.ErrorIs(t, err, errTxnSigHasNoSig) + require.Equal(t, uint64(0), batchSigs) mod++ _, signedTxns, secrets, addrs := generateTestObjects(numOfTxns, 20, 0, 50) @@ -353,6 +352,7 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E= txnGroups[mod][0].Msig = mSigTxn[0].Msig batchSigs, err = UnverifiedTxnSigJob{TxnGroup: txnGroups[mod]}.GetNumberOfBatchableItems() require.ErrorIs(t, err, errTxnSigNotWellFormed) + require.Equal(t, uint64(0), batchSigs) } // TestStreamToBatchPoolShutdown tests what happens when the exec pool shuts down @@ -437,10 +437,11 @@ func TestStreamToBatchPoolShutdown(t *testing.T) { //nolint:paralleltest // Not // send txn groups to be verified go func() { defer wg.Done() + outer: for _, tg := range txnGroups { select { case <-ctx.Done(): - break + break outer case inputChan <- &UnverifiedTxnSigJob{TxnGroup: tg, BacklogMessage: nil}: } } @@ -493,6 +494,7 @@ func TestStreamToBatchRestart(t *testing.T) { // send txn groups to be verified go func() { defer wg.Done() + outer: for i, tg := range txnGroups { if (i+1)%10 == 0 { cancel() @@ -502,7 +504,7 @@ func TestStreamToBatchRestart(t *testing.T) { } select { case <-ctx2.Done(): - break + break outer case inputChan <- &UnverifiedTxnSigJob{TxnGroup: tg, BacklogMessage: nil}: } } @@ -798,7 +800,10 @@ func TestStreamToBatchPostVBlocked(t *testing.T) { func TestStreamToBatchMakeStreamToBatchErr(t *testing.T) { partitiontest.PartitionTest(t) - _, err := MakeSigVerifyJobProcessor(&DummyLedgerForSignature{badHdr: true}, nil, nil, nil) + _, err := MakeSigVerifier(&DummyLedgerForSignature{badHdr: true}, nil) + require.Error(t, err) + + _, err = MakeSigVerifyJobProcessor(&DummyLedgerForSignature{badHdr: true}, nil, nil, nil) require.Error(t, err) } @@ -863,7 +868,7 @@ func TestGetErredUnprocessed(t *testing.T) { droppedChan := make(chan *UnverifiedTxnSigJob, 1) svh := txnSigBatchProcessor{ - resultChan: make(chan<- *VerificationResult, 0), + resultChan: make(chan<- *VerificationResult), droppedChan: droppedChan, } @@ -871,3 +876,32 @@ func TestGetErredUnprocessed(t *testing.T) { dropped := <-droppedChan require.Equal(t, *dropped, UnverifiedTxnSigJob{}) } + +func TestSigVerifier(t *testing.T) { + partitiontest.PartitionTest(t) + + numOfTxns := 16 + txnGroups, badTxnGroups := getSignedTransactions(numOfTxns, numOfTxns, 0, 0) + require.GreaterOrEqual(t, len(txnGroups), 1) + require.Equal(t, len(badTxnGroups), 0) + txnGroup := txnGroups[0] + + verificationPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, t) + defer verificationPool.Shutdown() + + cache := MakeVerifiedTransactionCache(50000) + + verifier, err := MakeSigVerifier(&DummyLedgerForSignature{}, cache) + require.NoError(t, err) + + err = verifier.Verify(txnGroup) + require.NoError(t, err) + + txnGroups, badTxnGroups = getSignedTransactions(numOfTxns, numOfTxns, 0, 1) + require.GreaterOrEqual(t, len(txnGroups), 1) + require.Greater(t, len(badTxnGroups), 0) + txnGroup = txnGroups[0] + + err = verifier.Verify(txnGroup) + require.Error(t, err) +} diff --git a/data/txHandler.go b/data/txHandler.go index eae9586c47..7ee5764137 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -132,6 +132,9 @@ type TxHandler struct { erl *util.ElasticRateLimiter appLimiter *appRateLimiter appLimiterBacklogThreshold int + + // batchVerifier provides synchronous verification of transaction groups, used only by pubsub validation in validateIncomingTxMessage. + batchVerifier verify.TxnGroupBatchSigVerifier } // TxHandlerOpts is TxHandler configuration options @@ -209,6 +212,13 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) { } } + // prepare the batch processor for pubsub synchronous verification + var err0 error + handler.batchVerifier, err0 = verify.MakeSigVerifier(handler.ledger, handler.ledger.VerifiedTransactionCache()) + if err0 != nil { + return nil, err0 + } + // prepare the transaction stream verifier var err error txnElementProcessor, err := verify.MakeSigVerifyJobProcessor(handler.ledger, handler.ledger.VerifiedTransactionCache(), @@ -246,16 +256,14 @@ func (handler *TxHandler) Start() { }) // libp2p pubsub validator and handler abstracted as TaggedMessageProcessor - handler.net.RegisterProcessors([]network.TaggedMessageProcessor{ + handler.net.RegisterValidatorHandlers([]network.TaggedMessageValidatorHandler{ { Tag: protocol.TxnTag, // create anonymous struct to hold the two functions and satisfy the network.MessageProcessor interface MessageHandler: struct { - network.ProcessorValidateFunc - network.ProcessorHandleFunc + network.ValidateHandleFunc }{ - network.ProcessorValidateFunc(handler.validateIncomingTxMessage), - network.ProcessorHandleFunc(handler.processIncomingTxMessage), + network.ValidateHandleFunc(handler.validateIncomingTxMessage), }, }, }) @@ -348,7 +356,7 @@ func (handler *TxHandler) backlogWorker() { } continue } - // handler.streamVerifierChan does not receive if ctx is cancled + // handler.streamVerifierChan does not receive if ctx is cancelled select { case handler.streamVerifierChan <- &verify.UnverifiedTxnSigJob{TxnGroup: wi.unverifiedTxGroup, BacklogMessage: wi}: case <-handler.ctx.Done(): @@ -550,7 +558,7 @@ func (handler *TxHandler) deleteFromCaches(msgKey *crypto.Digest, canonicalKey * // dedupCanonical checks if the transaction group has been seen before after reencoding to canonical representation. // returns a key used for insertion if the group was not found. -func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.SignedTxn, consumed int) (key *crypto.Digest, isDup bool) { +func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.SignedTxn, consumed int) (key *crypto.Digest, reencoded []byte, isDup bool) { // consider situations where someone want to censor transactions A // 1. Txn A is not part of a group => txn A with a valid signature is OK // Censorship attempts are: @@ -567,14 +575,16 @@ func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.Signed // - using individual txn from a group: {A, Z} could be poisoned by {A, B}, where B is invalid var d crypto.Digest + var reencodedBuf []byte ntx := len(unverifiedTxGroup) if ntx == 1 { // a single transaction => cache/dedup canonical txn with its signature enc := unverifiedTxGroup[0].MarshalMsg(nil) d = crypto.Hash(enc) if handler.txCanonicalCache.CheckAndPut(&d) { - return nil, true + return nil, nil, true } + reencodedBuf = enc } else { // a transaction group => cache/dedup the entire group canonical group encodeBuf := make([]byte, 0, unverifiedTxGroup[0].Msgsize()*ntx) @@ -585,14 +595,15 @@ func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.Signed // reallocated, some assumption on size was wrong // log and skip logging.Base().Warnf("Decoded size %d does not match to encoded %d", consumed, len(encodeBuf)) - return nil, false + return nil, nil, false } d = crypto.Hash(encodeBuf) if handler.txCanonicalCache.CheckAndPut(&d) { - return nil, true + return nil, nil, true } + reencodedBuf = encodeBuf } - return &d, false + return &d, reencodedBuf, false } // incomingMsgDupCheck runs the duplicate check on a raw incoming message. @@ -687,28 +698,32 @@ func decodeMsg(data []byte) (unverifiedTxGroup []transactions.SignedTxn, consume return unverifiedTxGroup, consumed, false } -// incomingTxGroupDupRateLimit checks -// - if the incoming transaction group has been seen before after reencoding to canonical representation, and -// - if the sender is rate limited by the per-application rate limiter. -func (handler *TxHandler) incomingTxGroupDupRateLimit(unverifiedTxGroup []transactions.SignedTxn, encodedExpectedSize int, sender network.DisconnectablePeer) (*crypto.Digest, bool) { +// incomingTxGroupCanonicalDedup checks if the incoming transaction group has been seen before after reencoding to canonical representation. +// It also return canonical representation of the transaction group allowing the caller to compare it with the input. +func (handler *TxHandler) incomingTxGroupCanonicalDedup(unverifiedTxGroup []transactions.SignedTxn, encodedExpectedSize int) (*crypto.Digest, []byte, bool) { var canonicalKey *crypto.Digest + var reencoded []byte if handler.txCanonicalCache != nil { var isDup bool - if canonicalKey, isDup = handler.dedupCanonical(unverifiedTxGroup, encodedExpectedSize); isDup { + if canonicalKey, reencoded, isDup = handler.dedupCanonical(unverifiedTxGroup, encodedExpectedSize); isDup { transactionMessagesDupCanonical.Inc(nil) - return canonicalKey, true + return nil, nil, true } } + return canonicalKey, reencoded, false +} +// incomingTxGroupAppRateLimit checks if the sender is rate limited by the per-application rate limiter. +func (handler *TxHandler) incomingTxGroupAppRateLimit(unverifiedTxGroup []transactions.SignedTxn, sender network.DisconnectablePeer) bool { // rate limit per application in a group. Limiting any app in a group drops the entire message. if handler.appLimiter != nil { congestedARL := len(handler.backlogQueue) > handler.appLimiterBacklogThreshold if congestedARL && handler.appLimiter.shouldDrop(unverifiedTxGroup, sender.(network.IPAddressable).RoutingAddr()) { transactionMessagesAppLimiterDrop.Inc(nil) - return canonicalKey, true + return true } } - return canonicalKey, false + return false } // processIncomingTxn decodes a transaction group from incoming message and enqueues into the back log for processing. @@ -744,13 +759,17 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net return network.OutgoingMessage{Action: network.Disconnect} } - canonicalKey, drop := handler.incomingTxGroupDupRateLimit(unverifiedTxGroup, consumed, rawmsg.Sender) + canonicalKey, _, drop := handler.incomingTxGroupCanonicalDedup(unverifiedTxGroup, consumed) if drop { // this re-serialized txgroup was detected as a duplicate by the canonical message cache, // or it was rate-limited by the per-app rate limiter return network.OutgoingMessage{Action: network.Ignore} } + if handler.incomingTxGroupAppRateLimit(unverifiedTxGroup, rawmsg.Sender) { + return network.OutgoingMessage{Action: network.Ignore} + } + select { case handler.backlogQueue <- &txBacklogMsg{ rawmsg: &rawmsg, @@ -772,65 +791,84 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net return network.OutgoingMessage{Action: network.Ignore} } -type validatedIncomingTxMessage struct { - rawmsg network.IncomingMessage - unverifiedTxGroup []transactions.SignedTxn - msgKey *crypto.Digest - canonicalKey *crypto.Digest -} - // validateIncomingTxMessage is the validator for the MessageProcessor implementation used by P2PNetwork. -func (handler *TxHandler) validateIncomingTxMessage(rawmsg network.IncomingMessage) network.ValidatedMessage { +func (handler *TxHandler) validateIncomingTxMessage(rawmsg network.IncomingMessage) network.OutgoingMessage { msgKey, isDup := handler.incomingMsgDupCheck(rawmsg.Data) if isDup { - return network.ValidatedMessage{Action: network.Ignore, ValidatedMessage: nil} + return network.OutgoingMessage{Action: network.Ignore} } unverifiedTxGroup, consumed, invalid := decodeMsg(rawmsg.Data) if invalid { // invalid encoding or exceeding txgroup, disconnect from this peer - return network.ValidatedMessage{Action: network.Disconnect, ValidatedMessage: nil} + return network.OutgoingMessage{Action: network.Disconnect} } - canonicalKey, drop := handler.incomingTxGroupDupRateLimit(unverifiedTxGroup, consumed, rawmsg.Sender) + canonicalKey, reencoded, drop := handler.incomingTxGroupCanonicalDedup(unverifiedTxGroup, consumed) if drop { - // this re-serialized txgroup was detected as a duplicate by the canonical message cache, - // or it was rate-limited by the per-app rate limiter - return network.ValidatedMessage{Action: network.Ignore, ValidatedMessage: nil} + return network.OutgoingMessage{Action: network.Ignore} } - return network.ValidatedMessage{ - Action: network.Accept, - Tag: rawmsg.Tag, - ValidatedMessage: &validatedIncomingTxMessage{ - rawmsg: rawmsg, - unverifiedTxGroup: unverifiedTxGroup, - msgKey: msgKey, - canonicalKey: canonicalKey, - }, + if handler.incomingTxGroupAppRateLimit(unverifiedTxGroup, rawmsg.Sender) { + return network.OutgoingMessage{Action: network.Ignore} } -} -// processIncomingTxMessage is the handler for the MessageProcessor implementation used by P2PNetwork. -func (handler *TxHandler) processIncomingTxMessage(validatedMessage network.ValidatedMessage) network.OutgoingMessage { - msg := validatedMessage.ValidatedMessage.(*validatedIncomingTxMessage) - select { - case handler.backlogQueue <- &txBacklogMsg{ - rawmsg: &msg.rawmsg, - unverifiedTxGroup: msg.unverifiedTxGroup, - rawmsgDataHash: msg.msgKey, - unverifiedTxGroupHash: msg.canonicalKey, + if reencoded == nil { + reencoded = reencode(unverifiedTxGroup) + } + + if !bytes.Equal(rawmsg.Data, reencoded) { + // reject non-canonically encoded messages + return network.OutgoingMessage{Action: network.Disconnect} + } + + // apply backlog worker logic + + wi := &txBacklogMsg{ + rawmsg: &rawmsg, + unverifiedTxGroup: unverifiedTxGroup, + rawmsgDataHash: msgKey, + unverifiedTxGroupHash: canonicalKey, capguard: nil, - }: - default: - // if we failed here we want to increase the corresponding metric. It might suggest that we - // want to increase the queue size. - transactionMessagesDroppedFromBacklog.Inc(nil) + } - // additionally, remove the txn from duplicate caches to ensure it can be re-submitted - handler.deleteFromCaches(msg.msgKey, msg.canonicalKey) + if handler.checkAlreadyCommitted(wi) { + transactionMessagesAlreadyCommitted.Inc(nil) + return network.OutgoingMessage{ + Action: network.Ignore, + } + } + + err := handler.batchVerifier.Verify(wi.unverifiedTxGroup) + if err != nil { + handler.postProcessReportErrors(err) + logging.Base().Warnf("Received a malformed tx group %v: %v", wi.unverifiedTxGroup, err) + return network.OutgoingMessage{ + Action: network.Disconnect, + } + } + verifiedTxGroup := wi.unverifiedTxGroup + + // save the transaction, if it has high enough fee and not already in the cache + err = handler.txPool.Remember(verifiedTxGroup) + if err != nil { + handler.rememberReportErrors(err) + logging.Base().Debugf("could not remember tx: %v", err) + return network.OutgoingMessage{ + Action: network.Ignore, + } + } + + transactionMessagesRemember.Inc(nil) + + // if we remembered without any error ( i.e. txpool wasn't full ), then we should pin these transactions. + err = handler.ledger.VerifiedTransactionCache().Pin(verifiedTxGroup) + if err != nil { + logging.Base().Infof("unable to pin transaction: %v", err) + } + return network.OutgoingMessage{ + Action: network.Accept, } - return network.OutgoingMessage{Action: network.Ignore} } var errBackLogFullLocal = errors.New("backlog full") diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 23235f15fd..24e09b2963 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -646,42 +646,42 @@ func TestTxHandlerProcessIncomingGroup(t *testing.T) { } } +func craftNonCanonical(t *testing.T, stxn *transactions.SignedTxn, blobStxn []byte) []byte { + // make non-canonical encoding and ensure it is not accepted + stxnNonCanTxn := transactions.SignedTxn{Txn: stxn.Txn} + blobTxn := protocol.Encode(&stxnNonCanTxn) + stxnNonCanAuthAddr := transactions.SignedTxn{AuthAddr: stxn.AuthAddr} + blobAuthAddr := protocol.Encode(&stxnNonCanAuthAddr) + stxnNonCanAuthSig := transactions.SignedTxn{Sig: stxn.Sig} + blobSig := protocol.Encode(&stxnNonCanAuthSig) + + if blobStxn == nil { + blobStxn = protocol.Encode(stxn) + } + + // double check our skills for transactions.SignedTxn creation by creating a new canonical encoding and comparing to the original + blobValidation := make([]byte, 0, len(blobTxn)+len(blobAuthAddr)+len(blobSig)) + blobValidation = append(blobValidation[:], blobAuthAddr...) + blobValidation = append(blobValidation[:], blobSig[1:]...) // cut transactions.SignedTxn's field count + blobValidation = append(blobValidation[:], blobTxn[1:]...) // cut transactions.SignedTxn's field count + blobValidation[0] += 2 // increase field count + require.Equal(t, blobStxn, blobValidation) + + // craft non-canonical + blobNonCan := make([]byte, 0, len(blobTxn)+len(blobAuthAddr)+len(blobSig)) + blobNonCan = append(blobNonCan[:], blobTxn...) + blobNonCan = append(blobNonCan[:], blobAuthAddr[1:]...) // cut transactions.SignedTxn's field count + blobNonCan = append(blobNonCan[:], blobSig[1:]...) // cut transactions.SignedTxn's field count + blobNonCan[0] += 2 // increase field count + require.Len(t, blobNonCan, len(blobStxn)) + require.NotEqual(t, blobStxn, blobNonCan) + return blobNonCan +} + func TestTxHandlerProcessIncomingCensoring(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - craftNonCanonical := func(t *testing.T, stxn *transactions.SignedTxn, blobStxn []byte) []byte { - // make non-canonical encoding and ensure it is not accepted - stxnNonCanTxn := transactions.SignedTxn{Txn: stxn.Txn} - blobTxn := protocol.Encode(&stxnNonCanTxn) - stxnNonCanAuthAddr := transactions.SignedTxn{AuthAddr: stxn.AuthAddr} - blobAuthAddr := protocol.Encode(&stxnNonCanAuthAddr) - stxnNonCanAuthSig := transactions.SignedTxn{Sig: stxn.Sig} - blobSig := protocol.Encode(&stxnNonCanAuthSig) - - if blobStxn == nil { - blobStxn = protocol.Encode(stxn) - } - - // double check our skills for transactions.SignedTxn creation by creating a new canonical encoding and comparing to the original - blobValidation := make([]byte, 0, len(blobTxn)+len(blobAuthAddr)+len(blobSig)) - blobValidation = append(blobValidation[:], blobAuthAddr...) - blobValidation = append(blobValidation[:], blobSig[1:]...) // cut transactions.SignedTxn's field count - blobValidation = append(blobValidation[:], blobTxn[1:]...) // cut transactions.SignedTxn's field count - blobValidation[0] += 2 // increase field count - require.Equal(t, blobStxn, blobValidation) - - // craft non-canonical - blobNonCan := make([]byte, 0, len(blobTxn)+len(blobAuthAddr)+len(blobSig)) - blobNonCan = append(blobNonCan[:], blobTxn...) - blobNonCan = append(blobNonCan[:], blobAuthAddr[1:]...) // cut transactions.SignedTxn's field count - blobNonCan = append(blobNonCan[:], blobSig[1:]...) // cut transactions.SignedTxn's field count - blobNonCan[0] += 2 // increase field count - require.Len(t, blobNonCan, len(blobStxn)) - require.NotEqual(t, blobStxn, blobNonCan) - return blobNonCan - } - forgeSig := func(t *testing.T, stxn *transactions.SignedTxn, blobStxn []byte) (transactions.SignedTxn, []byte) { stxnForged := *stxn crypto.RandBytes(stxnForged.Sig[:]) @@ -1012,6 +1012,29 @@ func TestTxHandlerProcessIncomingCacheBacklogDrop(t *testing.T) { require.Equal(t, initialValue+1, currentValue) } +func makeTxns(addresses []basics.Address, secrets []*crypto.SignatureSecrets, sendIdx, recvIdx int, gh crypto.Digest) ([]transactions.SignedTxn, []byte) { + note := make([]byte, 2) + crypto.RandBytes(note) + tx := transactions.Transaction{ + Type: protocol.PaymentTx, + Header: transactions.Header{ + Sender: addresses[sendIdx], + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: 0, + LastValid: basics.Round(proto.MaxTxnLife), + Note: note, + GenesisHash: gh, + }, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: addresses[recvIdx], + Amount: basics.MicroAlgos{Raw: mockBalancesMinBalance + (rand.Uint64() % 10000)}, + }, + } + signedTx := tx.Sign(secrets[sendIdx]) + blob := protocol.Encode(&signedTx) + return []transactions.SignedTxn{signedTx}, blob +} + func TestTxHandlerProcessIncomingCacheTxPoolDrop(t *testing.T) { partitiontest.PartitionTest(t) @@ -1048,27 +1071,7 @@ loop: } } - makeTxns := func(sendIdx, recvIdx int) ([]transactions.SignedTxn, []byte) { - tx := transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: addresses[sendIdx], - Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, - FirstValid: 0, - LastValid: basics.Round(proto.MaxTxnLife), - Note: make([]byte, 2), - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: addresses[recvIdx], - Amount: basics.MicroAlgos{Raw: mockBalancesMinBalance + (rand.Uint64() % 10000)}, - }, - } - signedTx := tx.Sign(secrets[sendIdx]) - blob := protocol.Encode(&signedTx) - return []transactions.SignedTxn{signedTx}, blob - } - - stxns, blob := makeTxns(1, 2) + stxns, blob := makeTxns(addresses, secrets, 1, 2, genesisHash) action := handler.processIncomingTxn(network.IncomingMessage{Data: blob}) require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action) @@ -2750,3 +2753,72 @@ func TestTxHandlerCapGuard(t *testing.T) { require.Eventually(t, func() bool { return completed.Load() }, 1*time.Second, 10*time.Millisecond) } + +func TestTxHandlerValidateIncomingTxMessage(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + const numUsers = 10 + addresses, secrets, genesis := makeTestGenesisAccounts(t, numUsers) + genBal := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr) + + ledgerName := fmt.Sprintf("%s-mem", t.Name()) + const inMem = true + log := logging.TestingLog(t) + log.SetLevel(logging.Panic) + + cfg := config.GetDefaultLocal() + ledger, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg) + require.NoError(t, err) + defer ledger.Close() + + handler, err := makeTestTxHandler(ledger, cfg) + require.NoError(t, err) + + // valid message + _, blob := makeTxns(addresses, secrets, 1, 2, genesisHash) + outmsg := handler.validateIncomingTxMessage(network.IncomingMessage{Data: blob}) + require.Equal(t, outmsg.Action, network.Accept) + + // non-canonical message + // for some reason craftNonCanonical cannot handle makeTxns output so make a simpler random txn + stxns, blob := makeRandomTransactions(1) + stxn := stxns[0] + blobNonCan := craftNonCanonical(t, &stxn, blob) + outmsg = handler.validateIncomingTxMessage(network.IncomingMessage{Data: blobNonCan}) + require.Equal(t, outmsg.Action, network.Disconnect) + + // invalid signature + stxns, _ = makeTxns(addresses, secrets, 1, 2, genesisHash) + stxns[0].Sig[0] = stxns[0].Sig[0] + 1 + blob2 := protocol.Encode(&stxns[0]) + outmsg = handler.validateIncomingTxMessage(network.IncomingMessage{Data: blob2}) + require.Equal(t, outmsg.Action, network.Disconnect) + + // invalid message + _, blob = makeTxns(addresses, secrets, 1, 2, genesisHash) + blob[0] = blob[0] + 1 + outmsg = handler.validateIncomingTxMessage(network.IncomingMessage{Data: blob}) + require.Equal(t, outmsg.Action, network.Disconnect) + + t.Run("with-canonical", func(t *testing.T) { + // make sure the reencoding from the canonical dedup checker's reencoding buf is correctly reused + cfg.TxIncomingFilteringFlags = 2 + require.True(t, cfg.TxFilterCanonicalEnabled()) + handler, err := makeTestTxHandler(ledger, cfg) + require.NoError(t, err) + + // valid message + _, blob := makeTxns(addresses, secrets, 1, 2, genesisHash) + outmsg := handler.validateIncomingTxMessage(network.IncomingMessage{Data: blob}) + require.Equal(t, outmsg.Action, network.Accept) + + // non-canonical message + // for some reason craftNonCanonical cannot handle makeTxns output so make a simpler random txn + stxns, blob := makeRandomTransactions(1) + stxn := stxns[0] + blobNonCan := craftNonCanonical(t, &stxn, blob) + outmsg = handler.validateIncomingTxMessage(network.IncomingMessage{Data: blobNonCan}) + require.Equal(t, outmsg.Action, network.Disconnect) + }) +} diff --git a/network/gossipNode.go b/network/gossipNode.go index 8b108b5fde..86a7b42c55 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -81,8 +81,10 @@ type GossipNode interface { // ClearHandlers deregisters all the existing message handlers. ClearHandlers() - // RegisterProcessors adds to the set of given message processors. - RegisterProcessors(dispatch []TaggedMessageProcessor) + // RegisterValidatorHandlers adds to the set of given message validation handlers. + // A difference with regular handlers is validation ones perform synchronous validation. + // Currently used as p2p pubsub topic validators. + RegisterValidatorHandlers(dispatch []TaggedMessageValidatorHandler) // ClearProcessors deregisters all the existing message processors. ClearProcessors() @@ -157,14 +159,6 @@ type OutgoingMessage struct { OnRelease func() } -// ValidatedMessage is a message that has been validated and is ready to be processed. -// Think as an intermediate one between IncomingMessage and OutgoingMessage -type ValidatedMessage struct { - Action ForwardingPolicy - Tag Tag - ValidatedMessage interface{} -} - // ForwardingPolicy is an enum indicating to whom we should send a message // //msgp:ignore ForwardingPolicy @@ -203,28 +197,19 @@ func (f HandlerFunc) Handle(message IncomingMessage) OutgoingMessage { return f(message) } -// MessageProcessor takes a IncomingMessage (e.g., vote, transaction), processes it, and returns what (if anything) +// MessageValidatorHandler takes a IncomingMessage (e.g., vote, transaction), processes it, and returns what (if anything) // to send to the network in response. -// This is an extension of the MessageHandler that works in two stages: validate ->[result]-> handle. -type MessageProcessor interface { - Validate(message IncomingMessage) ValidatedMessage - Handle(message ValidatedMessage) OutgoingMessage +// it supposed to perform synchronous validation and return the result of the validation +// so that network knows immediately if the message should be be broadcasted or not. +type MessageValidatorHandler interface { + ValidateHandle(message IncomingMessage) OutgoingMessage } -// ProcessorValidateFunc represents an implementation of the MessageProcessor interface -type ProcessorValidateFunc func(message IncomingMessage) ValidatedMessage - -// ProcessorHandleFunc represents an implementation of the MessageProcessor interface -type ProcessorHandleFunc func(message ValidatedMessage) OutgoingMessage - -// Validate implements MessageProcessor.Validate, calling the validator with the IncomingMessage and returning the action -// and validation extra data that can be use as the handler input. -func (f ProcessorValidateFunc) Validate(message IncomingMessage) ValidatedMessage { - return f(message) -} +// ValidateHandleFunc represents an implementation of the MessageProcessor interface +type ValidateHandleFunc func(message IncomingMessage) OutgoingMessage -// Handle implements MessageProcessor.Handle calling the handler with the ValidatedMessage and returning the OutgoingMessage -func (f ProcessorHandleFunc) Handle(message ValidatedMessage) OutgoingMessage { +// ValidateHandle implements MessageValidatorHandler.ValidateHandle, calling the validator with the IncomingMessage and returning the action. +func (f ValidateHandleFunc) ValidateHandle(message IncomingMessage) OutgoingMessage { return f(message) } @@ -236,9 +221,9 @@ type taggedMessageDispatcher[T any] struct { // TaggedMessageHandler receives one type of broadcast messages type TaggedMessageHandler = taggedMessageDispatcher[MessageHandler] -// TaggedMessageProcessor receives one type of broadcast messages +// TaggedMessageValidatorHandler receives one type of broadcast messages // and performs two stage processing: validating and handling -type TaggedMessageProcessor = taggedMessageDispatcher[MessageProcessor] +type TaggedMessageValidatorHandler = taggedMessageDispatcher[MessageValidatorHandler] // Propagate is a convenience function to save typing in the common case of a message handler telling us to propagate an incoming message // "return network.Propagate(msg)" instead of "return network.OutgoingMsg{network.Broadcast, msg.Tag, msg.Data}" diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go index c955dfff1a..7d5814ff39 100644 --- a/network/hybridNetwork.go +++ b/network/hybridNetwork.go @@ -192,10 +192,10 @@ func (n *HybridP2PNetwork) ClearHandlers() { n.wsNetwork.ClearHandlers() } -// RegisterProcessors adds to the set of given message processors. -func (n *HybridP2PNetwork) RegisterProcessors(dispatch []TaggedMessageProcessor) { - n.p2pNetwork.RegisterProcessors(dispatch) - n.wsNetwork.RegisterProcessors(dispatch) +// RegisterValidatorHandlers adds to the set of given message processors. +func (n *HybridP2PNetwork) RegisterValidatorHandlers(dispatch []TaggedMessageValidatorHandler) { + n.p2pNetwork.RegisterValidatorHandlers(dispatch) + n.wsNetwork.RegisterValidatorHandlers(dispatch) } // ClearProcessors deregisters all the existing message processors. diff --git a/network/multiplexer.go b/network/multiplexer.go index dc38fba277..ca40f9b0e4 100644 --- a/network/multiplexer.go +++ b/network/multiplexer.go @@ -24,15 +24,15 @@ import ( // Multiplexer is a message handler that sorts incoming messages by Tag and passes // them along to the relevant message handler for that type of message. type Multiplexer struct { - msgHandlers atomic.Value // stores map[Tag]MessageHandler, an immutable map. - msgProcessors atomic.Value // stores map[Tag]MessageProcessor, an immutable map. + msgHandlers atomic.Value // stores map[Tag]MessageHandler, an immutable map. + msgValidatorHandlers atomic.Value // stores map[Tag]MessageValidatorHandler, an immutable map. } // MakeMultiplexer creates an empty Multiplexer func MakeMultiplexer() *Multiplexer { m := &Multiplexer{} - m.ClearHandlers(nil) // allocate the map - m.ClearProcessors(nil) // allocate the map + m.ClearHandlers(nil) // allocate the map + m.ClearValidatorHandlers(nil) // allocate the map return m } @@ -60,9 +60,9 @@ func (m *Multiplexer) getHandler(tag Tag) (MessageHandler, bool) { return getHandler[MessageHandler](&m.msgHandlers, tag) } -// Retrieves the processor for the given message Tag from the processors array. -func (m *Multiplexer) getProcessor(tag Tag) (MessageProcessor, bool) { - return getHandler[MessageProcessor](&m.msgProcessors, tag) +// Retrieves the validating handler for the given message Tag from the validating handlers array. +func (m *Multiplexer) getValidatorHandler(tag Tag) (MessageValidatorHandler, bool) { + return getHandler[MessageValidatorHandler](&m.msgValidatorHandlers, tag) } // Handle is the "input" side of the multiplexer. It dispatches the message to the previously defined handler. @@ -73,18 +73,10 @@ func (m *Multiplexer) Handle(msg IncomingMessage) OutgoingMessage { return OutgoingMessage{} } -// Validate is an alternative "input" side of the multiplexer. It dispatches the message to the previously defined validator. -func (m *Multiplexer) Validate(msg IncomingMessage) ValidatedMessage { - if handler, ok := m.getProcessor(msg.Tag); ok { - return handler.Validate(msg) - } - return ValidatedMessage{} -} - -// Process is the second step of message handling after validation. It dispatches the message to the previously defined processor. -func (m *Multiplexer) Process(msg ValidatedMessage) OutgoingMessage { - if handler, ok := m.getProcessor(msg.Tag); ok { - return handler.Handle(msg) +// ValidateHandle is an alternative "input" side of the multiplexer. It dispatches the message to the previously defined validator. +func (m *Multiplexer) ValidateHandle(msg IncomingMessage) OutgoingMessage { + if handler, ok := m.getValidatorHandler(msg.Tag); ok { + return handler.ValidateHandle(msg) } return OutgoingMessage{} } @@ -110,9 +102,9 @@ func (m *Multiplexer) RegisterHandlers(dispatch []TaggedMessageHandler) { registerMultiplexer(&m.msgHandlers, dispatch) } -// RegisterProcessors registers the set of given message handlers. -func (m *Multiplexer) RegisterProcessors(dispatch []TaggedMessageProcessor) { - registerMultiplexer(&m.msgProcessors, dispatch) +// RegisterValidatorHandlers registers the set of given message handlers. +func (m *Multiplexer) RegisterValidatorHandlers(dispatch []TaggedMessageValidatorHandler) { + registerMultiplexer(&m.msgValidatorHandlers, dispatch) } func clearMultiplexer[T any](target *atomic.Value, excludeTags []Tag) { @@ -143,7 +135,7 @@ func (m *Multiplexer) ClearHandlers(excludeTags []Tag) { clearMultiplexer[MessageHandler](&m.msgHandlers, excludeTags) } -// ClearProcessors deregisters all the existing message handlers other than the one provided in the excludeTags list -func (m *Multiplexer) ClearProcessors(excludeTags []Tag) { - clearMultiplexer[MessageProcessor](&m.msgProcessors, excludeTags) +// ClearValidatorHandlers deregisters all the existing message handlers other than the one provided in the excludeTags list +func (m *Multiplexer) ClearValidatorHandlers(excludeTags []Tag) { + clearMultiplexer[MessageValidatorHandler](&m.msgValidatorHandlers, excludeTags) } diff --git a/network/p2p/pubsub.go b/network/p2p/pubsub.go index a968bcb6a9..657baecdde 100644 --- a/network/p2p/pubsub.go +++ b/network/p2p/pubsub.go @@ -98,6 +98,7 @@ func makePubSub(ctx context.Context, cfg config.Local, host host.Host) (*pubsub. pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign), // pubsub.WithValidateThrottle(cfg.TxBacklogSize), pubsub.WithValidateWorkers(incomingThreads), + pubsub.WithRawTracer(pubsubTracer{}), } return pubsub.NewGossipSub(ctx, host, options...) diff --git a/network/p2p/pubsubTracer.go b/network/p2p/pubsubTracer.go new file mode 100644 index 0000000000..ca57bc69ce --- /dev/null +++ b/network/p2p/pubsubTracer.go @@ -0,0 +1,98 @@ +// Copyright (C) 2019-2024 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 ( + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + + "github.com/algorand/go-algorand/util/metrics" +) + +var _ = pubsub.RawTracer(pubsubTracer{}) + +var transactionMessagesP2PRejectMessage = metrics.NewTagCounter(metrics.TransactionMessagesP2PRejectMessage.Name, metrics.TransactionMessagesP2PRejectMessage.Description) +var transactionMessagesP2PDuplicateMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PDuplicateMessage) +var transactionMessagesP2PDeliverMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PDeliverMessage) +var transactionMessagesP2PUnderdeliverableMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PUndeliverableMessage) +var transactionMessagesP2PValidateMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PValidateMessage) + +// pubsubTracer is a tracer for pubsub events used to track metrics. +type pubsubTracer struct{} + +// AddPeer is invoked when a new peer is added. +func (t pubsubTracer) AddPeer(p peer.ID, proto protocol.ID) {} + +// RemovePeer is invoked when a peer is removed. +func (t pubsubTracer) RemovePeer(p peer.ID) {} + +// Join is invoked when a new topic is joined +func (t pubsubTracer) Join(topic string) {} + +// Leave is invoked when a topic is abandoned +func (t pubsubTracer) Leave(topic string) {} + +// Graft is invoked when a new peer is grafted on the mesh (gossipsub) +func (t pubsubTracer) Graft(p peer.ID, topic string) {} + +// Prune is invoked when a peer is pruned from the message (gossipsub) +func (t pubsubTracer) Prune(p peer.ID, topic string) {} + +// ValidateMessage is invoked when a message first enters the validation pipeline. +func (t pubsubTracer) ValidateMessage(msg *pubsub.Message) { + transactionMessagesP2PValidateMessage.Inc(nil) +} + +// DeliverMessage is invoked when a message is delivered +func (t pubsubTracer) DeliverMessage(msg *pubsub.Message) { + transactionMessagesP2PDeliverMessage.Inc(nil) +} + +// RejectMessage is invoked when a message is Rejected or Ignored. +// The reason argument can be one of the named strings Reject*. +func (t pubsubTracer) RejectMessage(msg *pubsub.Message, reason string) { + switch reason { + case pubsub.RejectValidationThrottled, pubsub.RejectValidationQueueFull, pubsub.RejectValidationFailed, pubsub.RejectValidationIgnored: + transactionMessagesP2PRejectMessage.Add(reason, 1) + default: + transactionMessagesP2PRejectMessage.Add("other", 1) + } +} + +// DuplicateMessage is invoked when a duplicate message is dropped. +func (t pubsubTracer) DuplicateMessage(msg *pubsub.Message) { + transactionMessagesP2PDuplicateMessage.Inc(nil) +} + +// ThrottlePeer is invoked when a peer is throttled by the peer gater. +func (t pubsubTracer) ThrottlePeer(p peer.ID) {} + +// RecvRPC is invoked when an incoming RPC is received. +func (t pubsubTracer) RecvRPC(rpc *pubsub.RPC) {} + +// SendRPC is invoked when a RPC is sent. +func (t pubsubTracer) SendRPC(rpc *pubsub.RPC, p peer.ID) {} + +// DropRPC is invoked when an outbound RPC is dropped, typically because of a queue full. +func (t pubsubTracer) DropRPC(rpc *pubsub.RPC, p peer.ID) {} + +// UndeliverableMessage is invoked when the consumer of Subscribe is not reading messages fast enough and +// the pressure release mechanism trigger, dropping messages. +func (t pubsubTracer) UndeliverableMessage(msg *pubsub.Message) { + transactionMessagesP2PUnderdeliverableMessage.Inc(nil) +} diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index a186426b15..37c6cfcd52 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -693,14 +693,14 @@ func (n *P2PNetwork) ClearHandlers() { n.handler.ClearHandlers([]Tag{}) } -// RegisterProcessors adds to the set of given message handlers. -func (n *P2PNetwork) RegisterProcessors(dispatch []TaggedMessageProcessor) { - n.handler.RegisterProcessors(dispatch) +// RegisterValidatorHandlers adds to the set of given message handlers. +func (n *P2PNetwork) RegisterValidatorHandlers(dispatch []TaggedMessageValidatorHandler) { + n.handler.RegisterValidatorHandlers(dispatch) } // ClearProcessors deregisters all the existing message handlers. func (n *P2PNetwork) ClearProcessors() { - n.handler.ClearProcessors([]Tag{}) + n.handler.ClearValidatorHandlers([]Tag{}) } // GetHTTPClient returns a http.Client with a suitable for the network Transport @@ -916,7 +916,8 @@ func (n *P2PNetwork) txTopicHandleLoop() { n.log.Debugf("Subscribed to topic %s", p2p.TXTopicName) for { - msg, err := sub.Next(n.ctx) + // msg from sub.Next not used since all work done by txTopicValidator + _, err := sub.Next(n.ctx) if err != nil { if err != pubsub.ErrSubscriptionCancelled && err != context.Canceled { n.log.Errorf("Error reading from subscription %v, peerId %s", err, n.service.ID()) @@ -925,13 +926,6 @@ func (n *P2PNetwork) txTopicHandleLoop() { sub.Cancel() return } - // if there is a self-sent the message no need to process it. - if msg.ReceivedFrom == n.service.ID() { - continue - } - - _ = n.handler.Process(msg.ValidatorData.(ValidatedMessage)) - // participation or configuration change, cancel subscription and quit if !n.wantTXGossip.Load() { n.log.Debugf("Cancelling subscription to topic %s due participation change", p2p.TXTopicName) @@ -978,7 +972,7 @@ func (n *P2PNetwork) txTopicValidator(ctx context.Context, peerID peer.ID, msg * peerStats.txReceived.Add(1) n.peerStatsMu.Unlock() - outmsg := n.handler.Validate(inmsg) + outmsg := n.handler.ValidateHandle(inmsg) // there was a decision made in the handler about this message switch outmsg.Action { case Ignore: diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index f6eea2ab69..7cb35a0e82 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -111,26 +111,22 @@ func TestP2PSubmitTX(t *testing.T) { // now we should be connected in a line: B <-> A <-> C where both B and C are connected to A but not each other // Since we aren't using the transaction handler in this test, we need to register a pass-through handler - passThroughHandler := []TaggedMessageProcessor{ + passThroughHandler := []TaggedMessageValidatorHandler{ { Tag: protocol.TxnTag, MessageHandler: struct { - ProcessorValidateFunc - ProcessorHandleFunc + ValidateHandleFunc }{ - ProcessorValidateFunc(func(msg IncomingMessage) ValidatedMessage { - return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: nil} - }), - ProcessorHandleFunc(func(msg ValidatedMessage) OutgoingMessage { - return OutgoingMessage{Action: Ignore} + ValidateHandleFunc(func(msg IncomingMessage) OutgoingMessage { + return OutgoingMessage{Action: Accept, Tag: msg.Tag} }), }, }, } - netA.RegisterProcessors(passThroughHandler) - netB.RegisterProcessors(passThroughHandler) - netC.RegisterProcessors(passThroughHandler) + netA.RegisterValidatorHandlers(passThroughHandler) + netB.RegisterValidatorHandlers(passThroughHandler) + netC.RegisterValidatorHandlers(passThroughHandler) // send messages from B and confirm that they get received by C (via A) for i := 0; i < 10; i++ { @@ -206,25 +202,21 @@ func TestP2PSubmitTXNoGossip(t *testing.T) { // ensure netC cannot receive messages - passThroughHandler := []TaggedMessageProcessor{ + passThroughHandler := []TaggedMessageValidatorHandler{ { Tag: protocol.TxnTag, MessageHandler: struct { - ProcessorValidateFunc - ProcessorHandleFunc + ValidateHandleFunc }{ - ProcessorValidateFunc(func(msg IncomingMessage) ValidatedMessage { - return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: nil} - }), - ProcessorHandleFunc(func(msg ValidatedMessage) OutgoingMessage { - return OutgoingMessage{Action: Ignore} + ValidateHandleFunc(func(msg IncomingMessage) OutgoingMessage { + return OutgoingMessage{Action: Accept, Tag: msg.Tag} }), }, }, } - netB.RegisterProcessors(passThroughHandler) - netC.RegisterProcessors(passThroughHandler) + netB.RegisterValidatorHandlers(passThroughHandler) + netC.RegisterValidatorHandlers(passThroughHandler) for i := 0; i < 10; i++ { err = netA.Broadcast(context.Background(), protocol.TxnTag, []byte(fmt.Sprintf("test %d", i)), false, nil) require.NoError(t, err) @@ -860,26 +852,22 @@ func TestP2PRelay(t *testing.T) { return netA.hasPeers() && netB.hasPeers() }, 2*time.Second, 50*time.Millisecond) - makeCounterHandler := func(numExpected int, counter *atomic.Uint32, msgs *[][]byte) ([]TaggedMessageProcessor, chan struct{}) { + makeCounterHandler := func(numExpected int, counter *atomic.Uint32, msgs *[][]byte) ([]TaggedMessageValidatorHandler, chan struct{}) { counterDone := make(chan struct{}) - counterHandler := []TaggedMessageProcessor{ + counterHandler := []TaggedMessageValidatorHandler{ { Tag: protocol.TxnTag, MessageHandler: struct { - ProcessorValidateFunc - ProcessorHandleFunc + ValidateHandleFunc }{ - ProcessorValidateFunc(func(msg IncomingMessage) ValidatedMessage { - return ValidatedMessage{Action: Accept, Tag: msg.Tag, ValidatedMessage: msg.Data} - }), - ProcessorHandleFunc(func(msg ValidatedMessage) OutgoingMessage { + ValidateHandleFunc(func(msg IncomingMessage) OutgoingMessage { if msgs != nil { - *msgs = append(*msgs, msg.ValidatedMessage.([]byte)) + *msgs = append(*msgs, msg.Data) } if count := counter.Add(1); int(count) >= numExpected { close(counterDone) } - return OutgoingMessage{Action: Ignore} + return OutgoingMessage{Action: Accept, Tag: msg.Tag} }), }, }, @@ -888,7 +876,7 @@ func TestP2PRelay(t *testing.T) { } var counter atomic.Uint32 counterHandler, counterDone := makeCounterHandler(1, &counter, nil) - netA.RegisterProcessors(counterHandler) + netA.RegisterValidatorHandlers(counterHandler) // send 5 messages from netB to netA // since relaying is disabled on net B => no messages should be received by net A @@ -943,7 +931,7 @@ func TestP2PRelay(t *testing.T) { var loggedMsgs [][]byte counterHandler, counterDone = makeCounterHandler(expectedMsgs, &counter, &loggedMsgs) netA.ClearProcessors() - netA.RegisterProcessors(counterHandler) + netA.RegisterValidatorHandlers(counterHandler) for i := 0; i < expectedMsgs/2; i++ { err := netB.Relay(context.Background(), protocol.TxnTag, []byte{5, 6, 7, byte(i)}, true, nil) diff --git a/network/wsNetwork.go b/network/wsNetwork.go index fc42296652..917d1b6e64 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -124,6 +124,7 @@ var networkIncomingBufferMicros = metrics.MakeCounter(metrics.MetricName{Name: " var networkHandleMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_rx_handle_micros_total", Description: "microseconds spent by protocol handlers in the receive thread"}) var networkBroadcasts = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcasts_total", Description: "number of broadcast operations"}) +var networkBroadcastQueueFull = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_queue_full_total", Description: "number of messages that were drops due to full broadcast queue"}) var networkBroadcastQueueMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_queue_micros_total", Description: "microseconds broadcast requests sit on queue"}) var networkBroadcastSendMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_send_micros_total", Description: "microseconds spent broadcasting"}) var networkBroadcastsDropped = metrics.MakeCounter(metrics.MetricName{Name: "algod_broadcasts_dropped_total", Description: "number of broadcast messages not sent to any peer"}) @@ -135,7 +136,6 @@ var networkPeerAlreadyClosed = metrics.MakeCounter(metrics.MetricName{Name: "alg var networkSlowPeerDrops = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_slow_drops_total", Description: "number of peers dropped for being slow to send to"}) var networkIdlePeerDrops = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_idle_drops_total", Description: "number of peers dropped due to idle connection"}) -var networkBroadcastQueueFull = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_queue_full_total", Description: "number of messages that were drops due to full broadcast queue"}) var minPing = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peer_min_ping_seconds", Description: "Network round trip time to fastest peer in seconds."}) var meanPing = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peer_mean_ping_seconds", Description: "Network round trip time to average peer in seconds."}) @@ -859,8 +859,8 @@ func (wn *WebsocketNetwork) ClearHandlers() { wn.handler.ClearHandlers([]Tag{protocol.PingTag, protocol.PingReplyTag, protocol.NetPrioResponseTag}) } -// RegisterProcessors registers the set of given message handlers. -func (wn *WebsocketNetwork) RegisterProcessors(dispatch []TaggedMessageProcessor) { +// RegisterValidatorHandlers registers the set of given message handlers. +func (wn *WebsocketNetwork) RegisterValidatorHandlers(dispatch []TaggedMessageValidatorHandler) { } // ClearProcessors deregisters all the existing message handlers. diff --git a/test/e2e-go/features/p2p/p2p_basic_test.go b/test/e2e-go/features/p2p/p2p_basic_test.go index 6f3e8aae47..5ee2f034ae 100644 --- a/test/e2e-go/features/p2p/p2p_basic_test.go +++ b/test/e2e-go/features/p2p/p2p_basic_test.go @@ -17,6 +17,7 @@ package p2p import ( + "crypto/rand" "path/filepath" "testing" "time" @@ -28,7 +29,7 @@ import ( "github.com/stretchr/testify/require" ) -func testP2PWithConfig(t *testing.T, cfgname string) { +func testP2PWithConfig(t *testing.T, templateName string) *fixtures.RestClientFixture { r := require.New(fixtures.SynchronizedTest(t)) var fixture fixtures.RestClientFixture @@ -42,22 +43,88 @@ func testP2PWithConfig(t *testing.T, cfgname string) { consensus[protocol.ConsensusCurrentVersion] = fastProtocol fixture.SetConsensus(consensus) - fixture.Setup(t, filepath.Join("nettemplates", cfgname)) - defer fixture.ShutdownImpl(true) // preserve logs in testdir - + fixture.Setup(t, filepath.Join("nettemplates", templateName)) _, err := fixture.NC.AlgodClient() r.NoError(err) err = fixture.WaitForRound(10, 30*time.Second) r.NoError(err) + + return &fixture } func TestP2PTwoNodes(t *testing.T) { partitiontest.PartitionTest(t) - testP2PWithConfig(t, "TwoNodes50EachP2P.json") + fixture := testP2PWithConfig(t, "TwoNodes50EachP2P.json") + defer fixture.Shutdown() + + // ensure transaction propagation on both directions + pingClient := fixture.LibGoalClient + pingAccountList, err := fixture.GetWalletsSortedByBalance() + require.NoError(t, err) + pingAccount := pingAccountList[0].Address + + pongClient := fixture.GetLibGoalClientForNamedNode("Node") + pongAccounts, err := fixture.GetNodeWalletsSortedByBalance(pongClient) + require.NoError(t, err) + pongAccount := pongAccounts[0].Address + + pingBalance, err := pingClient.GetBalance(pingAccount) + require.NoError(t, err) + pongBalance, err := pingClient.GetBalance(pongAccount) + require.NoError(t, err) + + require.Equal(t, pingBalance, pongBalance) + + expectedPingBalance := pingBalance + expectedPongBalance := pongBalance + + minTxnFee, minAcctBalance, err := fixture.CurrentMinFeeAndBalance() + require.NoError(t, err) + + transactionFee := minTxnFee + 5 + amountPongSendsPing := minAcctBalance + amountPingSendsPong := minAcctBalance * 3 / 2 + + pongTxidsToAddresses := make(map[string]string) + pingTxidsToAddresses := make(map[string]string) + + randNote := func(tb testing.TB) []byte { + b := make([]byte, 8) + _, err := rand.Read(b) + require.NoError(tb, err) + return b + } + + for i := 0; i < 5; i++ { + pongTx, err := pongClient.SendPaymentFromUnencryptedWallet(pongAccount, pingAccount, transactionFee, amountPongSendsPing, randNote(t)) + pongTxidsToAddresses[pongTx.ID().String()] = pongAccount + require.NoError(t, err) + pingTx, err := pingClient.SendPaymentFromUnencryptedWallet(pingAccount, pongAccount, transactionFee, amountPingSendsPong, randNote(t)) + pingTxidsToAddresses[pingTx.ID().String()] = pingAccount + require.NoError(t, err) + expectedPingBalance = expectedPingBalance - transactionFee - amountPingSendsPong + amountPongSendsPing + expectedPongBalance = expectedPongBalance - transactionFee - amountPongSendsPing + amountPingSendsPong + } + curStatus, _ := pongClient.Status() + curRound := curStatus.LastRound + + fixture.AlgodClient = fixture.GetAlgodClientForController(fixture.GetNodeControllerForDataDir(pongClient.DataDir())) + confirmed := fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) + require.True(t, confirmed, "failed to see confirmed ping transaction by round %v", curRound+uint64(5)) + confirmed = fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) + require.True(t, confirmed, "failed to see confirmed pong transaction by round %v", curRound+uint64(5)) + + pingBalance, err = pongClient.GetBalance(pingAccount) + require.NoError(t, err) + pongBalance, err = pongClient.GetBalance(pongAccount) + require.NoError(t, err) + require.True(t, expectedPingBalance <= pingBalance, "ping balance is different than expected.") + require.True(t, expectedPongBalance <= pongBalance, "pong balance is different than expected.") } func TestP2PFiveNodes(t *testing.T) { partitiontest.PartitionTest(t) - testP2PWithConfig(t, "FiveNodesP2P.json") + fixture := testP2PWithConfig(t, "FiveNodesP2P.json") + defer fixture.Shutdown() } diff --git a/util/metrics/metrics.go b/util/metrics/metrics.go index eb867729cf..fcc566312f 100644 --- a/util/metrics/metrics.go +++ b/util/metrics/metrics.go @@ -128,6 +128,17 @@ var ( // 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"} + // TransactionMessagesP2PRejectMessage "Number of rejected p2p pubsub transaction messages" + TransactionMessagesP2PRejectMessage = MetricName{Name: "algod_transaction_messages_p2p_reject", Description: "Number of rejected p2p pubsub transaction messages"} + // TransactionMessagesP2PDuplicateMessage "Number of duplicate p2p pubsub transaction messages"} + TransactionMessagesP2PDuplicateMessage = MetricName{Name: "algod_transaction_messages_p2p_duplicate", Description: "Number of duplicate p2p pubsub transaction messages"} + // TransactionMessagesP2PDeliverMessage "Number of delivered p2p pubsub transaction messages" + TransactionMessagesP2PDeliverMessage = MetricName{Name: "algod_transaction_messages_p2p_delivered", Description: "Number of delivered p2p pubsub transaction messages"} + // TransactionMessagesP2PUndeliverableMessage "Number of undeliverable p2p pubsub transaction messages" + TransactionMessagesP2PUndeliverableMessage = MetricName{Name: "algod_transaction_messages_p2p_undeliverable", Description: "Number of undeliverable p2p pubsub transaction messages"} + // TransactionMessagesP2PValidateMessage "Number of p2p pubsub transaction messages received for validation" + TransactionMessagesP2PValidateMessage = MetricName{Name: "algod_transaction_messages_p2p_validate", Description: "Number of p2p pubsub transaction messages received for validation"} + // TransactionGroupTxSyncHandled "Number of transaction groups handled via txsync" TransactionGroupTxSyncHandled = MetricName{Name: "algod_transaction_group_txsync_handled", Description: "Number of transaction groups handled via txsync"} // TransactionGroupTxSyncRemember "Number of transaction groups remembered via txsync" From 595ec23e5a49413d1744b7f3620a45cbd849ff42 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:10:30 -0400 Subject: [PATCH 199/218] network: remove ws net proto 2.1 (#6081) --- network/msgCompressor.go | 55 +++++----------------------- network/msgCompressor_test.go | 39 +------------------- network/wsNetwork.go | 69 ++++++----------------------------- network/wsNetwork_test.go | 46 ++++++----------------- network/wsPeer.go | 16 ++++---- 5 files changed, 43 insertions(+), 182 deletions(-) diff --git a/network/msgCompressor.go b/network/msgCompressor.go index 831b509aef..61108cad99 100644 --- a/network/msgCompressor.go +++ b/network/msgCompressor.go @@ -31,28 +31,6 @@ var zstdCompressionMagic = [4]byte{0x28, 0xb5, 0x2f, 0xfd} const zstdCompressionLevel = zstd.BestSpeed -// checkCanCompress checks if there is an proposal payload message and peers supporting compression -func checkCanCompress(request broadcastRequest, peers []*wsPeer) bool { - canCompress := false - hasPP := false - for _, tag := range request.tags { - if tag == protocol.ProposalPayloadTag { - hasPP = true - break - } - } - // if have proposal payload check if there are any peers supporting compression - if hasPP { - for _, peer := range peers { - if peer.pfProposalCompressionSupported() { - canCompress = true - break - } - } - } - return canCompress -} - // zstdCompressMsg returns a concatenation of a tag and compressed data func zstdCompressMsg(tbytes []byte, d []byte) ([]byte, string) { bound := zstd.CompressBound(len(d)) @@ -89,13 +67,7 @@ type wsPeerMsgDataConverter struct { ppdec zstdProposalDecompressor } -type zstdProposalDecompressor struct { - active bool -} - -func (dec zstdProposalDecompressor) enabled() bool { - return dec.active -} +type zstdProposalDecompressor struct{} func (dec zstdProposalDecompressor) accept(data []byte) bool { return len(data) > 4 && bytes.Equal(data[:4], zstdCompressionMagic[:]) @@ -126,18 +98,16 @@ func (dec zstdProposalDecompressor) convert(data []byte) ([]byte, error) { func (c *wsPeerMsgDataConverter) convert(tag protocol.Tag, data []byte) ([]byte, error) { if tag == protocol.ProposalPayloadTag { - if c.ppdec.enabled() { - // sender might support compressed payload but fail to compress for whatever reason, - // in this case it sends non-compressed payload - the receiver decompress only if it is compressed. - if c.ppdec.accept(data) { - res, err := c.ppdec.convert(data) - if err != nil { - return nil, fmt.Errorf("peer %s: %w", c.origin, err) - } - return res, nil + // sender might support compressed payload but fail to compress for whatever reason, + // in this case it sends non-compressed payload - the receiver decompress only if it is compressed. + if c.ppdec.accept(data) { + res, err := c.ppdec.convert(data) + if err != nil { + return nil, fmt.Errorf("peer %s: %w", c.origin, err) } - c.log.Warnf("peer %s supported zstd but sent non-compressed data", c.origin) + return res, nil } + c.log.Warnf("peer %s supported zstd but sent non-compressed data", c.origin) } return data, nil } @@ -148,11 +118,6 @@ func makeWsPeerMsgDataConverter(wp *wsPeer) *wsPeerMsgDataConverter { origin: wp.originAddress, } - if wp.pfProposalCompressionSupported() { - c.ppdec = zstdProposalDecompressor{ - active: true, - } - } - + c.ppdec = zstdProposalDecompressor{} return &c } diff --git a/network/msgCompressor_test.go b/network/msgCompressor_test.go index 3b08b5fc0e..172cf05a98 100644 --- a/network/msgCompressor_test.go +++ b/network/msgCompressor_test.go @@ -48,37 +48,6 @@ func TestZstdDecompress(t *testing.T) { require.Nil(t, decompressed) } -func TestCheckCanCompress(t *testing.T) { - partitiontest.PartitionTest(t) - - req := broadcastRequest{} - peers := []*wsPeer{} - r := checkCanCompress(req, peers) - require.False(t, r) - - req.tags = []protocol.Tag{protocol.AgreementVoteTag} - r = checkCanCompress(req, peers) - require.False(t, r) - - req.tags = []protocol.Tag{protocol.AgreementVoteTag, protocol.ProposalPayloadTag} - r = checkCanCompress(req, peers) - require.False(t, r) - - peer1 := wsPeer{ - features: 0, - } - peers = []*wsPeer{&peer1} - r = checkCanCompress(req, peers) - require.False(t, r) - - peer2 := wsPeer{ - features: pfCompressedProposal, - } - peers = []*wsPeer{&peer1, &peer2} - r = checkCanCompress(req, peers) - require.True(t, r) -} - func TestZstdCompressMsg(t *testing.T) { partitiontest.PartitionTest(t) @@ -108,7 +77,7 @@ func TestWsPeerMsgDataConverterConvert(t *testing.T) { partitiontest.PartitionTest(t) c := wsPeerMsgDataConverter{} - c.ppdec = zstdProposalDecompressor{active: false} + c.ppdec = zstdProposalDecompressor{} tag := protocol.AgreementVoteTag data := []byte("data") @@ -117,13 +86,9 @@ func TestWsPeerMsgDataConverterConvert(t *testing.T) { require.Equal(t, data, r) tag = protocol.ProposalPayloadTag - r, err = c.convert(tag, data) - require.NoError(t, err) - require.Equal(t, data, r) - l := converterTestLogger{} c.log = &l - c.ppdec = zstdProposalDecompressor{active: true} + c.ppdec = zstdProposalDecompressor{} r, err = c.convert(tag, data) require.NoError(t, err) require.Equal(t, data, r) diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 917d1b6e64..2af3a9b6bf 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -146,11 +146,6 @@ var peers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peers", De var incomingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_incoming_peers", Description: "Number of active incoming peers."}) var outgoingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_outgoing_peers", Description: "Number of active outgoing peers."}) -var networkPrioBatchesPPWithCompression = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_prio_batches_wpp_comp_sent_total", Description: "number of prio compressed batches with PP"}) -var networkPrioBatchesPPWithoutCompression = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_pp_prio_batches_wpp_non_comp_sent_total", Description: "number of prio non-compressed batches with PP"}) -var networkPrioPPCompressedSize = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_prio_pp_compressed_size_total", Description: "cumulative size of all compressed PP"}) -var networkPrioPPNonCompressedSize = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_prio_pp_non_compressed_size_total", Description: "cumulative size of all non-compressed PP"}) - // peerDisconnectionAckDuration defines the time we would wait for the peer disconnection to complete. const peerDisconnectionAckDuration = 5 * time.Second @@ -1062,6 +1057,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt wn.setHeaders(responseHeader) responseHeader.Set(ProtocolVersionHeader, matchingVersion) responseHeader.Set(GenesisHeader, wn.GenesisID) + // set the features we support responseHeader.Set(PeerFeaturesHeader, PeerFeatureProposalCompression) var challenge string if wn.prioScheme != nil { @@ -1391,21 +1387,10 @@ func (wn *WebsocketNetwork) getPeersChangeCounter() int32 { } // preparePeerData prepares batches of data for sending. -// It performs optional zstd compression for proposal massages -func (wn *msgBroadcaster) preparePeerData(request broadcastRequest, prio bool, peers []*wsPeer) ([][]byte, [][]byte, []crypto.Digest, bool) { - // determine if there is a payload proposal and peers supporting compressed payloads - wantCompression := false - containsPrioPPTag := false - if prio { - wantCompression = checkCanCompress(request, peers) - } - +// It performs zstd compression for proposal massages if they this is a prio request and has proposal. +func (wn *msgBroadcaster) preparePeerData(request broadcastRequest, prio bool) ([][]byte, []crypto.Digest) { digests := make([]crypto.Digest, len(request.data)) data := make([][]byte, len(request.data)) - var dataCompressed [][]byte - if wantCompression { - dataCompressed = make([][]byte, len(request.data)) - } for i, d := range request.data { tbytes := []byte(request.tags[i]) mbytes := make([]byte, len(tbytes)+len(d)) @@ -1416,29 +1401,15 @@ func (wn *msgBroadcaster) preparePeerData(request broadcastRequest, prio bool, p digests[i] = crypto.Hash(mbytes) } - if prio { - if request.tags[i] == protocol.ProposalPayloadTag { - networkPrioPPNonCompressedSize.AddUint64(uint64(len(d)), nil) - containsPrioPPTag = true - } - } - - if wantCompression { - if request.tags[i] == protocol.ProposalPayloadTag { - compressed, logMsg := zstdCompressMsg(tbytes, d) - if len(logMsg) > 0 { - wn.log.Warn(logMsg) - } else { - networkPrioPPCompressedSize.AddUint64(uint64(len(compressed)), nil) - } - dataCompressed[i] = compressed - } else { - // otherwise reuse non-compressed from above - dataCompressed[i] = mbytes + if prio && request.tags[i] == protocol.ProposalPayloadTag { + compressed, logMsg := zstdCompressMsg(tbytes, d) + if len(logMsg) > 0 { + wn.log.Warn(logMsg) } + data[i] = compressed } } - return data, dataCompressed, digests, containsPrioPPTag + return data, digests } // prio is set if the broadcast is a high-priority broadcast. @@ -1455,7 +1426,7 @@ func (wn *msgBroadcaster) innerBroadcast(request broadcastRequest, prio bool, pe } start := time.Now() - data, dataWithCompression, digests, containsPrioPPTag := wn.preparePeerData(request, prio, peers) + data, digests := wn.preparePeerData(request, prio) // first send to all the easy outbound peers who don't block, get them started. sentMessageCount := 0 @@ -1466,23 +1437,7 @@ func (wn *msgBroadcaster) innerBroadcast(request broadcastRequest, prio bool, pe if peer == request.except { continue } - var ok bool - if peer.pfProposalCompressionSupported() && len(dataWithCompression) > 0 { - // if this peer supports compressed proposals and compressed data batch is filled out, use it - ok = peer.writeNonBlockMsgs(request.ctx, dataWithCompression, prio, digests, request.enqueueTime) - if prio { - if containsPrioPPTag { - networkPrioBatchesPPWithCompression.Inc(nil) - } - } - } else { - ok = peer.writeNonBlockMsgs(request.ctx, data, prio, digests, request.enqueueTime) - if prio { - if containsPrioPPTag { - networkPrioBatchesPPWithoutCompression.Inc(nil) - } - } - } + ok := peer.writeNonBlockMsgs(request.ctx, data, prio, digests, request.enqueueTime) if ok { sentMessageCount++ continue @@ -1951,7 +1906,7 @@ const ProtocolVersionHeader = "X-Algorand-Version" const ProtocolAcceptVersionHeader = "X-Algorand-Accept-Version" // SupportedProtocolVersions contains the list of supported protocol versions by this node ( in order of preference ). -var SupportedProtocolVersions = []string{"2.2", "2.1"} +var SupportedProtocolVersions = []string{"2.2"} // ProtocolVersion is the current version attached to the ProtocolVersionHeader header /* Version history: diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 6af3a697fc..0128c28fc2 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -429,15 +429,12 @@ func TestWebsocketProposalPayloadCompression(t *testing.T) { } var tests []testDef = []testDef{ - // two old nodes - {[]string{"2.1"}, "2.1", []string{"2.1"}, "2.1"}, - // two new nodes with overwritten config {[]string{"2.2"}, "2.2", []string{"2.2"}, "2.2"}, // old node + new node {[]string{"2.1"}, "2.1", []string{"2.2", "2.1"}, "2.2"}, - {[]string{"2.2", "2.1"}, "2.2", []string{"2.1"}, "2.1"}, + {[]string{"2.2", "2.1"}, "2.1", []string{"2.2"}, "2.2"}, // combinations {[]string{"2.2", "2.1"}, "2.1", []string{"2.2", "2.1"}, "2.1"}, @@ -1101,7 +1098,7 @@ func TestDupFilter(t *testing.T) { defer netC.Stop() makeMsg := func(n int) []byte { - // We cannot harcode the msgSize to messageFilterSize + 1 because max allowed AV message is smaller than that. + // We cannot hardcode the msgSize to messageFilterSize + 1 because max allowed AV message is smaller than that. // We also cannot use maxSize for PP since it's a compressible tag but trying to compress random data will expand it. if messageFilterSize+1 < n { n = messageFilterSize + 1 @@ -1387,7 +1384,7 @@ func TestPeeringWithIdentityChallenge(t *testing.T) { assert.Equal(t, 0, len(netB.GetPeers(PeersConnectedOut))) // netA never attempts to set identity as it never sees a verified identity assert.Equal(t, 1, netA.identityTracker.(*mockIdentityTracker).getSetCount()) - // no connecton => netB does attepmt to add the identity to the tracker + // no connection => netB does attempt to add the identity to the tracker // and it would not end up being added assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getSetCount()) assert.Equal(t, 1, netB.identityTracker.(*mockIdentityTracker).getInsertCount()) @@ -1608,7 +1605,7 @@ func TestPeeringReceiverIdentityChallengeOnly(t *testing.T) { assert.Equal(t, 0, netB.identityTracker.(*mockIdentityTracker).getSetCount()) } -// TestPeeringIncorrectDeduplicationName confirm that if the reciever can't match +// TestPeeringIncorrectDeduplicationName confirm that if the receiver can't match // the Address in the challenge to its PublicAddress, identities aren't exchanged, but peering continues func TestPeeringIncorrectDeduplicationName(t *testing.T) { partitiontest.PartitionTest(t) @@ -1665,7 +1662,7 @@ func TestPeeringIncorrectDeduplicationName(t *testing.T) { // bi-directional connection would now work since netB detects to be connected to netA in tryConnectReserveAddr, // so force it. - // this second connection should set identities, because the reciever address matches now + // this second connection should set identities, because the receiver address matches now _, ok = netB.tryConnectReserveAddr(addrA) assert.False(t, ok) netB.wg.Add(1) @@ -2504,9 +2501,9 @@ func TestWebsocketNetwork_checkServerResponseVariables(t *testing.T) { } func (wn *WebsocketNetwork) broadcastWithTimestamp(tag protocol.Tag, data []byte, when time.Time) error { - msgArr := make([][]byte, 1, 1) + msgArr := make([][]byte, 1) msgArr[0] = data - tagArr := make([]protocol.Tag, 1, 1) + tagArr := make([]protocol.Tag, 1) tagArr[0] = tag request := broadcastRequest{tags: tagArr, data: msgArr, enqueueTime: when, ctx: context.Background()} @@ -3711,48 +3708,29 @@ func TestPreparePeerData(t *testing.T) { data: [][]byte{[]byte("test"), []byte("data")}, } - peers := []*wsPeer{} wn := WebsocketNetwork{} - data, comp, digests, seenPrioPPTag := wn.broadcaster.preparePeerData(req, false, peers) + data, digests := wn.broadcaster.preparePeerData(req, false) require.NotEmpty(t, data) - require.Empty(t, comp) require.NotEmpty(t, digests) require.Equal(t, len(req.data), len(digests)) require.Equal(t, len(data), len(digests)) - require.False(t, seenPrioPPTag) for i := range data { require.Equal(t, append([]byte(req.tags[i]), req.data[i]...), data[i]) } - // compression - peer1 := wsPeer{ - features: 0, - } - peer2 := wsPeer{ - features: pfCompressedProposal, - } - peers = []*wsPeer{&peer1, &peer2} - data, comp, digests, seenPrioPPTag = wn.broadcaster.preparePeerData(req, true, peers) + data, digests = wn.broadcaster.preparePeerData(req, true) require.NotEmpty(t, data) - require.NotEmpty(t, comp) require.NotEmpty(t, digests) require.Equal(t, len(req.data), len(digests)) require.Equal(t, len(data), len(digests)) - require.Equal(t, len(comp), len(digests)) - require.True(t, seenPrioPPTag) for i := range data { - require.Equal(t, append([]byte(req.tags[i]), req.data[i]...), data[i]) - } - - for i := range comp { if req.tags[i] != protocol.ProposalPayloadTag { - require.Equal(t, append([]byte(req.tags[i]), req.data[i]...), comp[i]) - require.Equal(t, data[i], comp[i]) + require.Equal(t, append([]byte(req.tags[i]), req.data[i]...), data[i]) + require.Equal(t, data[i], data[i]) } else { - require.NotEqual(t, data[i], comp[i]) - require.Equal(t, append([]byte(req.tags[i]), zstdCompressionMagic[:]...), comp[i][:len(req.tags[i])+len(zstdCompressionMagic)]) + require.Equal(t, append([]byte(req.tags[i]), zstdCompressionMagic[:]...), data[i][:len(req.tags[i])+len(zstdCompressionMagic)]) } } } diff --git a/network/wsPeer.go b/network/wsPeer.go index 2b302f071f..88a0c615f9 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -746,7 +746,7 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas wp.log.Warnf("wsPeer handleMessageOfInterest: could not unmarshall message from: %s %v", wp.conn.RemoteAddrString(), err) return true, disconnectBadData } - msgs := make([]sendMessage, 1, 1) + msgs := make([]sendMessage, 1) msgs[0] = sendMessage{ data: nil, enqueued: time.Now(), @@ -911,8 +911,8 @@ func (wp *wsPeer) writeLoopCleanup(reason disconnectReason) { } func (wp *wsPeer) writeNonBlock(ctx context.Context, data []byte, highPrio bool, digest crypto.Digest, msgEnqueueTime time.Time) bool { - msgs := make([][]byte, 1, 1) - digests := make([]crypto.Digest, 1, 1) + msgs := make([][]byte, 1) + digests := make([]crypto.Digest, 1) msgs[0] = data digests[0] = digest return wp.writeNonBlockMsgs(ctx, msgs, highPrio, digests, msgEnqueueTime) @@ -1090,7 +1090,7 @@ func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Re defer wp.getAndRemoveResponseChannel(hash) // Send serializedMsg - msg := make([]sendMessage, 1, 1) + msg := make([]sendMessage, 1) msg[0] = sendMessage{ data: append([]byte(tag), serializedMsg...), enqueued: time.Now(), @@ -1166,10 +1166,6 @@ func (wp *wsPeer) sendMessagesOfInterest(messagesOfInterestGeneration uint32, me } } -func (wp *wsPeer) pfProposalCompressionSupported() bool { - return wp.features&pfCompressedProposal != 0 -} - func (wp *wsPeer) OnClose(f func()) { if wp.closers == nil { wp.closers = []func(){} @@ -1180,7 +1176,9 @@ func (wp *wsPeer) OnClose(f func()) { //msgp:ignore peerFeatureFlag type peerFeatureFlag int -const pfCompressedProposal peerFeatureFlag = 1 +const ( + pfCompressedProposal peerFeatureFlag = 1 << iota +) // versionPeerFeatures defines protocol version when peer features were introduced const versionPeerFeatures = "2.2" From 23a04c280274df7e723a3c2306b68e02586708e6 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:49:42 -0400 Subject: [PATCH 200/218] tests: flaky tests fixes (#6098) --- ledger/ledger_test.go | 46 ++++++++++--------- network/p2p/p2p_test.go | 8 +++- .../scripts/e2e_subs/goal-partkey-commands.sh | 15 ++++-- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index c4bb74fcb5..e73d648b4d 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -2915,6 +2915,12 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi require.NoError(t, err) defer l.Close() + // quit the commitSyncer goroutine: this test flushes manually with triggerTrackerFlush + l.trackers.ctxCancel() + l.trackers.ctxCancel = nil + <-l.trackers.commitSyncerClosed + l.trackers.commitSyncerClosed = nil + blk := genesisInitState.Block sp := bookkeeping.StateProofTrackingData{ @@ -2929,6 +2935,9 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi blk.BlockHeader.Round++ err = l.AddBlock(blk, agreement.Certificate{}) require.NoError(t, err) + if i > 0 && i%100 == 0 { + triggerTrackerFlush(t, l) + } } // we simulate that the stateproof for round 512 is confirmed on chain, and we can move to the next one. @@ -2941,31 +2950,12 @@ func testVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T, cfg confi blk.BlockHeader.Round++ err = l.AddBlock(blk, agreement.Certificate{}) require.NoError(t, err) - } - - // wait all pending commits to finish - l.trackers.accountsWriting.Wait() - - // quit the commitSyncer goroutine: this test flushes manually with triggerTrackerFlush - l.trackers.ctxCancel() - l.trackers.ctxCancel = nil - <-l.trackers.commitSyncerClosed - l.trackers.commitSyncerClosed = nil - - // it is possible a commmit was scheduled while commitSyncer was closing so that there is one pending task - // that required to be done before before the ledger can be closed, so drain the queue -outer: - for { - select { - case <-l.trackers.deferredCommits: - log.Info("drained deferred commit") - l.trackers.accountsWriting.Done() - default: - break outer + if i%100 == 0 { + triggerTrackerFlush(t, l) } } - // flush one final time + // flush remaining blocks triggerTrackerFlush(t, l) var vtSnapshot map[basics.Round]*ledgercore.VotersForRound @@ -2982,6 +2972,18 @@ outer: require.NotContains(t, vtSnapshot, basics.Round(240)) }() + t.Log("reloading ledger") + // drain any deferred commits since AddBlock above triggered scheduleCommit +outer: + for { + select { + case <-l.trackers.deferredCommits: + l.trackers.accountsWriting.Done() + default: + break outer + } + } + err = l.reloadLedger() require.NoError(t, err) diff --git a/network/p2p/p2p_test.go b/network/p2p/p2p_test.go index 2da5782afc..021a5fbd8b 100644 --- a/network/p2p/p2p_test.go +++ b/network/p2p/p2p_test.go @@ -290,7 +290,13 @@ func TestP2PMakeHostAddressFilter(t *testing.T) { mala, err := multiaddr.NewMultiaddr(la) require.NoError(t, err) host.Network().Listen(mala) - require.Empty(t, host.Addrs()) + addrs := host.Addrs() + if len(addrs) > 0 { + // CI servers might have a single public IP interface, validate if this is a case + for _, a := range addrs { + require.True(t, manet.IsPublicAddr(a)) + } + } host.Close() } diff --git a/test/scripts/e2e_subs/goal-partkey-commands.sh b/test/scripts/e2e_subs/goal-partkey-commands.sh index dd60d44016..b333a0e8aa 100755 --- a/test/scripts/e2e_subs/goal-partkey-commands.sh +++ b/test/scripts/e2e_subs/goal-partkey-commands.sh @@ -63,7 +63,8 @@ fail_test () { create_and_fund_account () { set +x # disable command echoing to hide the account funding output - local TEMP_ACCT=$(${gcmd} account new|awk '{ print $6 }') + local TEMP_ACCT + TEMP_ACCT=$(${gcmd} account new|awk '{ print $6 }') SEND_OUTOUT=$(${gcmd} clerk send -f "$INITIAL_ACCOUNT" -t "$TEMP_ACCT" -a 1000000 2>&1) if [[ $SEND_OUTOUT == *"Couldn't broadcast tx"* ]]; then fail_test "Failed to fund account: $SEND_OUTOUT" @@ -77,17 +78,21 @@ create_and_fund_account () { # $2 - a participation id # $3 - error message verify_registered_state () { + SEARCH_STATE=$(echo "$1" | xargs) + SEARCH_KEY=$(echo "$2" | xargs) + SEARCH_INVOKE_CONTEXT=$(echo "$3" | xargs) + # look for participation ID anywhere in the partkeyinfo output PARTKEY_OUTPUT=$(${gcmd} account partkeyinfo) - if ! echo "$PARTKEY_OUTPUT" | grep -q "$2"; then - fail_test "Key $2 was not installed properly for cmd '$3':\n$PARTKEY_OUTPUT" + if ! echo "$PARTKEY_OUTPUT" | grep -q -F "$SEARCH_KEY"; then + fail_test "Key $SEARCH_KEY was not installed properly for cmd '$SEARCH_INVOKE_CONTEXT':\n$PARTKEY_OUTPUT" fi # looking for yes/no, and the 8 character head of participation id in this line: # yes LFMT...RHJQ 4UPT6AQC... 4 0 3000 LISTKEY_OUTPUT=$(${gcmd} account listpartkeys) - if ! echo "$LISTKEY_OUTPUT" | grep -q "$1.*$(echo "$2" | cut -c1-8)"; then - fail_test "Unexpected key $2 state ($1) for cmd '$3':\n$LISTKEY_OUTPUT" + if ! echo "$LISTKEY_OUTPUT" | grep -q "$SEARCH_STATE.*$(echo "$SEARCH_KEY" | cut -c1-8)"; then + fail_test "Unexpected key $SEARCH_KEY state (looked for $SEARCH_STATE ) for cmd '$SEARCH_INVOKE_CONTEXT':\n$LISTKEY_OUTPUT" fi } From 7fc243c2080486b8a3afb355d8d5bae8128985a3 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:04:07 -0400 Subject: [PATCH 201/218] docs: p2p package overview (#6096) Co-authored-by: Jason Paulos Co-authored-by: Gary Malouf <982483+gmalouf@users.noreply.github.com> --- catchup/ledgerFetcher.go | 6 +- catchup/universalFetcher.go | 6 +- network/README-P2P.md | 149 +++++++++++++++++++ network/limitcaller/rateLimitingTransport.go | 21 ++- network/p2p/README.md | 88 ++++++++++- network/p2p/capabilities.go | 16 +- network/p2p/capabilities_test.go | 2 +- network/p2p/p2p.go | 7 +- network/p2pNetwork.go | 5 +- network/p2pNetwork_test.go | 5 - network/p2pPeer.go | 18 +-- node/node.go | 4 +- 12 files changed, 279 insertions(+), 48 deletions(-) create mode 100644 network/README-P2P.md diff --git a/catchup/ledgerFetcher.go b/catchup/ledgerFetcher.go index 916627db8f..ae4c720108 100644 --- a/catchup/ledgerFetcher.go +++ b/catchup/ledgerFetcher.go @@ -81,7 +81,11 @@ func (lf *ledgerFetcher) requestLedger(ctx context.Context, peer network.HTTPPee } network.SetUserAgentHeader(request.Header) - return peer.GetHTTPClient().Do(request) + httpClient := peer.GetHTTPClient() + if httpClient == nil { + return nil, fmt.Errorf("requestLedger: HTTPPeer %s has no http client", peer.GetAddress()) + } + return httpClient.Do(request) } func (lf *ledgerFetcher) headLedger(ctx context.Context, peer network.Peer, round basics.Round) error { diff --git a/catchup/universalFetcher.go b/catchup/universalFetcher.go index 27b970fc26..c7a8a9a4cf 100644 --- a/catchup/universalFetcher.go +++ b/catchup/universalFetcher.go @@ -69,11 +69,15 @@ func (uf *universalBlockFetcher) fetchBlock(ctx context.Context, round basics.Ro } address = fetcherClient.address() } else if httpPeer, validHTTPPeer := peer.(network.HTTPPeer); validHTTPPeer { + httpClient := httpPeer.GetHTTPClient() + if httpClient == nil { + return nil, nil, time.Duration(0), fmt.Errorf("fetchBlock: HTTPPeer %s has no http client", httpPeer.GetAddress()) + } fetcherClient := &HTTPFetcher{ peer: httpPeer, rootURL: httpPeer.GetAddress(), net: uf.net, - client: httpPeer.GetHTTPClient(), + client: httpClient, log: uf.log, config: &uf.config} fetchedBuf, err = fetcherClient.getBlockBytes(ctx, round) diff --git a/network/README-P2P.md b/network/README-P2P.md new file mode 100644 index 0000000000..c67bd53273 --- /dev/null +++ b/network/README-P2P.md @@ -0,0 +1,149 @@ +# P2P Network implementation overview + +Refer to [p2p sub-package overview](./p2p/README.md) for details about p2p sub-components. + +`P2PNetwork` implements the `GossipNode` interface similarly to `WsNetwork`. Both use +the same peer connection management and message broadcast functions but different +transport: lip2p-managed connections and HTTP + WebSocket, respectively. +`P2PNetwork` and `WsNetwork` require `config.NetAddress` to be set in order to start a server. + +In addition, `HybridNetwork` is an aggregate of `P2PNetwork` and `WsNetwork` allowing a node +to interact over both networks. In the case of hybrid operation, both `config.P2PNetAddress` and +`config.NetAddress` are used. + +## General design + +`P2PNetwork` follows the `WsNetwork` approach for peers management and message handling: + - `msgHandler` used process or route the network protocol messages to external handlers + (for example, transaction handler or agreement service) + - `broadcaster` implementing the broadcast functionality (see below) + - mesh thread to maintain `GossipFanout` number of outgoing peers + - HTTP Server for external HTTP services (block, catchpoints) + - `OnNetworkAdvance` listener to react on round advancing + +A key difference is that `P2PNetwork` uses `go-libp2p-pubsub` for TX message handling. +Upon start it subscribes to `/algo/tx/0.1.0` topic and publishes TX messages as needed. +The `pubsub` library divides message handling into two stages: validation and processing. Based on +the validation result, a message is either discarded or accepted for further +broadcasting to other peers. This necessitates having separate handlers for TX messages +in `TxHandler`, as we must synchronously determine whether a transaction group is valid: + - can't ignore fast and broadcast later - will be rejected as a seen message + - can't accept fast to prevent invalid/expired transactions broadcasting + +## Major Components + +### HTTP Services + +`P2PNetwork` uses libp2p's `http` submodule to handle HTTP traffic over libp2p-managed connection. +It is `http.Handler`-compatible so that service handlers are registered the same way as for `WsNetwork`. + +### Phonebook and Peerstore and peer classes + +Originally phonebook was designed as an address registry holding permanent (`-p` cli option +or `phonebook.json` extra configuration file) and dynamic (SRV DNS records) entries. +These entries later can be later retrieved by a peer role +(`PhoneBookEntryRelayRole` or `PhoneBookEntryArchivalRole`). +A new `PeerStore` (built on top of `libp2p.Peerstore`) resembles the original `Phonebook` +by strictly implementing some of its methods and has the remaining `Phonebook`'s methods +with a slightly different signature - `string` vs `peer.AddrInfo` for address representation. +The main issue is that entries in `PeerStore` are identified by `PeerID` +and each peer might have multiple addresses (versus the original WS peers with the only one +`host:port` connectivity option.) + +Both P2PNetwork and WsNetwork have an extra level of peer classification on top of two phonebook's +classes: `PeersConnectedOut`, `PeersConnectedIn`, `PeersPhonebookRelays`, `PeersPhonebookArchivalNodes`. +This allows network clients to be more precise on peers set they want to work with. For example, +ledger service wants `PeersPhonebookArchivalNodes`, and transaction syncer - `PeersConnectedOut`. + + +### wsPeer + +Peers are created in `wsStreamHandler` that is called for both incoming and outgoing connections +(and streams). `incoming` flag is set to true for incoming connection. +At the very beginning of the `wsStreamHandler` one byte read/write happens in order to make sure: + - Stream is operable + - A placeholder for a handshake where some meta-data can be exchanged + +Each peer gets a read channel `handler.readBuffer` where it enqueues incoming messages for routing +to appropriate handler. + +Connected peers are maintained as a `wsPeers` map similarly to the `WsNetwork`. +The main difference between `P2PNetwork` and `WsNetwork` is `http.Client`. Because wsPeers operate +over the multiplexed streams in libp2p-managed connection, a plain `http.Client` would not be able +to connect to a p2p HTTP server. This requires the `wsPeer` constructed in `P2PNetwork` to have a special +libp2p-streams compatible `http.Client` produced by `MakeHTTPClientWithRateLimit` helper method. +It implements a rate-limiting approach similar to the regular http clients from `WsNetwork`. + +### Broadcaster + +`msgBroadcaster` encapsulates a shared broadcasting logic: priority vs bulk messages (and queues), +data preparation, peers retrieving. Broadcast requests eventually hits +`peer.writeNonBlockMsgs` -> `peer.writeLoopSendMsg` -> `conn.WriteMessage`. +See the diagram denoting the broadcast data flow. + +```mermaid +graph LR + + p2pnet[P2PNetwork] + wsnet[WsNetwork] + B[broadcaster] + + p2pnet & wsnet --> B + + subgraph "wsPeer" + direction LR + writeNonBlockMsgs + Conn[conn.WriteMessage] + + subgraph "writeLoop" + writeLoopSendMsg + end + + writeNonBlockMsgs --> writeLoop + writeLoopSendMsg --> Conn + end + + B --> writeNonBlockMsgs + + Conn --> WMP2P & WMWS + + subgraph "wsPeerConnP2P" + WMP2P[WriteMessage] + end + + subgraph "websocket" + WMWS[WriteMessage] + end + + subgraph "libp2p" + stream.Write + end + + WMP2P --> libp2p +``` + +### DHT and Capabilities discovery + +DHT is controlled by the `EnableDHTProviders` configuration option and the capabilities +exposed by a node. These capabilities include: + - `archival`: a listening node with `Archival` config flag set + - `catchpointStoring`: a listening node configured to store catchpoints + - `gossip`: a listening node with `EnableGossipService` config flag set + +When the `P2PNetwork` starts, the node begins advertising its capabilities by running +a background goroutine. By default, the underlying DHT implementation pulls bootstrap nodes from +a peer store and attempts to connect immediately, which is not how go-algorand services operate. +To address this, a new `bootstrapper` abstraction has been added to control bootstrap peer +access using the DHT's `BootstrapFunc` mechanism. The callback function returns empty bootstrap +peers until the `P2PNetwork` starts. + +### Net identity based peers deduplication + +`WsNetwork` net identity was slightly extended to allow ws and p2p nodes cross-check +when running in a hybrid mode: + - `identityTracker` instance is shared between `WsNetwork` and `P2PNetwork` + - identity schema supplied to the `WsNetwork` uses a p2p-node private key based message signer + - `PublicAddress` must be set for hybrid nodes in order to operate properly + +Using the changes above `identityTracker` is able to deduplicate `WsNetwork` peer if it ends up +to be hybrid node already connected to via `P2PNetwork` and other way around. diff --git a/network/limitcaller/rateLimitingTransport.go b/network/limitcaller/rateLimitingTransport.go index 45bc0725ed..de68c9b371 100644 --- a/network/limitcaller/rateLimitingTransport.go +++ b/network/limitcaller/rateLimitingTransport.go @@ -50,19 +50,16 @@ var ErrConnectionQueueingTimeout = errors.New("rateLimitingTransport: queueing t // according to the entries in the phonebook. func MakeRateLimitingTransport(phonebook ConnectionTimeStore, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int) RateLimitingTransport { defaultTransport := http.DefaultTransport.(*http.Transport) - return RateLimitingTransport{ - phonebook: phonebook, - innerTransport: &http.Transport{ - Proxy: defaultTransport.Proxy, - DialContext: dialer.innerDialContext, - MaxIdleConns: defaultTransport.MaxIdleConns, - IdleConnTimeout: defaultTransport.IdleConnTimeout, - TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout, - ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout, - MaxIdleConnsPerHost: maxIdleConnsPerHost, - }, - queueingTimeout: queueingTimeout, + innerTransport := &http.Transport{ + Proxy: defaultTransport.Proxy, + DialContext: dialer.innerDialContext, + MaxIdleConns: defaultTransport.MaxIdleConns, + IdleConnTimeout: defaultTransport.IdleConnTimeout, + TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout, + ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout, + MaxIdleConnsPerHost: maxIdleConnsPerHost, } + return MakeRateLimitingTransportWithRoundTripper(phonebook, queueingTimeout, innerTransport, nil, maxIdleConnsPerHost) } // MakeRateLimitingTransportWithRoundTripper creates a rate limiting http transport that would limit the requests rate diff --git a/network/p2p/README.md b/network/p2p/README.md index 8490e391b6..b95e5be32f 100644 --- a/network/p2p/README.md +++ b/network/p2p/README.md @@ -23,7 +23,7 @@ Libp2p also provides an implementation of a message-based gossip protocol, Gossi Algorand's current network protocol sends messages between peers over bidirectional WebSocket connections. Nodes that are configured to enable message-forwarding (including -nodes currently called "relays") validate incoming messages, then selectively forward +nodes currently called "relays") validate incoming messages, then selectively forward messages to other connected peers. This network implementation (`WebsocketNetwork`) sits behind the `GossipNode` interface in the network package. @@ -36,8 +36,8 @@ via peer connections managed by libp2p. The `P2PNetwork` implementation uses and [peer IDs](https://docs.libp2p.io/concepts/fundamentals/peers/#peer-ids-in-multiaddrs) to establish connections and identify peers. -Currently transactions (protocol tag `TX`) are distributed using the GossipSub protocol, -while all other messages are forwarded over a custom message protocol `/algorand-ws/1.0.0` +Currently transactions (protocol tag `TX`) are distributed using the GossipSub protocol (see [pubsub.go](./pubsub.go)), +while all other messages are forwarded over the pre-existing custom message protocol `/algorand-ws/1.0.0` (see [streams.go](./streams.go)) that uses the same message serialization as the existing `WebsocketNetwork` implementation. These two protocols are multiplexed over a single connection using libp2p streams. @@ -63,3 +63,85 @@ graph LR AW --> WS S --> T ``` + +The underlying libp2p implementation is abstracted as `p2p.Service` and is initialized in two steps: +1. Creating a p2p `Host` +2. Creating a service `serviceImpl` object + +`Host` is also used for p2p HTTP server and DHT Discovery service creation. It is also useful for unit testing. Note, `Host` is created with `NoListenAddrs` options that prevents automatic listening and networking until the `Service.Start()` is called. This follows the designs of Algod services (including the WsNetwork service). + +### Connection limiting + +libp2p's `ResourceManager` is used to limit the number of connections up to `cfg.P2PIncomingConnectionsLimit`. + +### DHT and capabilities + +Provides helper methods to construct DHT discovery service using `go-libp2p-kad-dht` library. +High level [CapabilitiesDiscovery](./capabilities.go) class supports retrieving (`PeersForCapability`) +peers by a given capability(-ies) or advertising own capabilities (`AdvertiseCapabilities`). + +Note, by default private and non-routable addresses are filtered (see `AddrsFactory`), +libp2p's `ObservedAddrManager` can track its own public address and makes it available +(and so that discoverable with DHT) if it was observed at least 4 times in 30 minutes (as of libp2p@v0.33.2). + +```mermaid +graph LR + + subgraph "node" + Cap[Capabilities] + end + + subgraph "P2P Implementation" + P2P[P2PNetwork] + AdvCap[AdvertiseCapabilities] + end + + P2P --> AdvCap + Cap -.-> P2P + + subgraph "libp2p" + Adv[Advertise] + Addr[Addrs] + OAM[ObservedAddrManager] + AF[AddrFactory] + KAD["/kad/1.0.0"] + end + + OAM -.-> Addr + AF -.-> Addr + AdvCap --> Adv + + subgraph "libp2p-kad-dht" + Pro[Provide] + end + + Addr -.-> Pro + Adv --> Pro + Pro --> KAD +``` + +### HTTP over libp2p connection + +libp2p@0.33 added ability to multiplex HTTP traffic in p2p connection. +A custom `/algorand-http/1.0.0` stream is utilized to expose HTTP server and allow +network service clients (catchup, catchpoint, txsync) to register its own handlers +similarly to the legacy ws-net implementation. + +### Peerstore + +In-memory peerstore implements `libp2p.Peerstore` and go-algorand `Phonebook` interfaces. +Peer classes (relays, archival, etc) and persistent peers (i.e. peers from command line or phonebook.json) +are supported. Possible enhancement is to save/load peerstore to/from disk to tolerate bootstrap nodes failures. + +### Logging + +lip2p uses zap logger as a separate `ipfs/go-log/v2` module. `EnableP2PLogging` helper adds +go-algorand's `logrus` as a custom zap core so that all libp2p logs go through go-algorand logging facility. +Unfortunately `ipfs/go-log/v2` has a primary logging core as module variable that makes impossible +to have custom `logrus` sub-loggers in unit tests. + +### Metrics + +`go-libp2p` uses Prometheus as a metrics library, `go-libp2p-kad-dht` relies on OpenCensus library. +go-algorand has two collectors (see `util/metrics`) for both Prometheus and OpenCensus for +counters and gauges with labels. Other types (summary, histogram, distribution) are not supported at the moment. \ No newline at end of file diff --git a/network/p2p/capabilities.go b/network/p2p/capabilities.go index e5781aa389..7a418767d1 100644 --- a/network/p2p/capabilities.go +++ b/network/p2p/capabilities.go @@ -56,13 +56,13 @@ type CapabilitiesDiscovery struct { wg sync.WaitGroup } -// Advertise implements the discovery.Discovery/discovery.Advertiser interface -func (c *CapabilitiesDiscovery) Advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) { +// advertise implements the discovery.Discovery/discovery.Advertiser interface +func (c *CapabilitiesDiscovery) advertise(ctx context.Context, ns string, opts ...discovery.Option) (time.Duration, error) { return c.disc.Advertise(ctx, ns, opts...) } -// FindPeers implements the discovery.Discovery/discovery.Discoverer interface -func (c *CapabilitiesDiscovery) FindPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) { +// findPeers implements the discovery.Discovery/discovery.Discoverer interface +func (c *CapabilitiesDiscovery) findPeers(ctx context.Context, ns string, opts ...discovery.Option) (<-chan peer.AddrInfo, error) { return c.disc.FindPeers(ctx, ns, opts...) } @@ -78,8 +78,8 @@ func (c *CapabilitiesDiscovery) Host() host.Host { return c.dht.Host() } -// AddPeer adds a given peer.AddrInfo to the Host's Peerstore, and the DHT's routing table -func (c *CapabilitiesDiscovery) AddPeer(p peer.AddrInfo) (bool, error) { +// addPeer adds a given peer.AddrInfo to the Host's Peerstore, and the DHT's routing table +func (c *CapabilitiesDiscovery) addPeer(p peer.AddrInfo) (bool, error) { c.Host().Peerstore().AddAddrs(p.ID, p.Addrs, libpeerstore.AddressTTL) return c.dht.RoutingTable().TryAddPeer(p.ID, true, true) } @@ -93,7 +93,7 @@ func (c *CapabilitiesDiscovery) PeersForCapability(capability Capability, n int) var peers []peer.AddrInfo // +1 because it can include self but we exclude self from the returned list // that might confuse the caller (and tests assertions) - peersChan, err := c.FindPeers(ctx, string(capability), discovery.Limit(n+1)) + peersChan, err := c.findPeers(ctx, string(capability), discovery.Limit(n+1)) if err != nil { return nil, err } @@ -128,7 +128,7 @@ func (c *CapabilitiesDiscovery) AdvertiseCapabilities(capabilities ...Capability var err error advertisementInterval := maxAdvertisementInterval for _, capa := range capabilities { - ttl, err0 := c.Advertise(c.dht.Context(), string(capa)) + ttl, err0 := c.advertise(c.dht.Context(), string(capa)) if err0 != nil { err = err0 c.log.Errorf("failed to advertise for capability %s: %v", capa, err0) diff --git a/network/p2p/capabilities_test.go b/network/p2p/capabilities_test.go index 881860f647..5b41ed70d8 100644 --- a/network/p2p/capabilities_test.go +++ b/network/p2p/capabilities_test.go @@ -62,7 +62,7 @@ func TestCapabilities_Discovery(t *testing.T) { for _, capD := range caps { peersAdded := 0 for _, addr := range addrs { - added, err := capD.AddPeer(addr) + added, err := capD.addPeer(addr) require.NoError(t, err) require.True(t, added) peersAdded++ diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index e908f148d8..2f8e00b911 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -61,7 +61,6 @@ type Service interface { IDSigner() *PeerIDChallengeSigner AddrInfo() peer.AddrInfo // return addrInfo for self - DialNode(context.Context, *peer.AddrInfo) error DialPeersUntilTargetCount(targetConnCount int) ClosePeer(peer.ID) error @@ -257,15 +256,15 @@ func (s *serviceImpl) DialPeersUntilTargetCount(targetConnCount int) { if len(s.host.Network().ConnsToPeer(peerInfo.ID)) > 0 { continue } - err := s.DialNode(context.Background(), peerInfo) // leaving the calls as blocking for now, to not over-connect beyond fanout + err := s.dialNode(context.Background(), peerInfo) // leaving the calls as blocking for now, to not over-connect beyond fanout if err != nil { s.log.Warnf("failed to connect to peer %s: %v", peerInfo.ID, err) } } } -// DialNode attempts to establish a connection to the provided peer -func (s *serviceImpl) DialNode(ctx context.Context, peer *peer.AddrInfo) error { +// dialNode attempts to establish a connection to the provided peer +func (s *serviceImpl) dialNode(ctx context.Context, peer *peer.AddrInfo) error { // don't try connecting to ourselves if peer.ID == s.host.ID() { return nil diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 37c6cfcd52..ab53f72e01 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -767,6 +767,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) if err != nil { + n.log.Warnf("Cannot construct HTTP Client for %s: %v", p2pPeer, err) client = nil } var netIdentPeerID algocrypto.PublicKey @@ -782,7 +783,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea peerCore := makePeerCore(ctx, n, n.log, n.handler.readBuffer, addr, client, addr) wsp := &wsPeer{ wsPeerCore: peerCore, - conn: &wsPeerConnP2PImpl{stream: stream}, + conn: &wsPeerConnP2P{stream: stream}, outgoing: !incoming, identity: netIdentPeerID, } @@ -844,7 +845,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea // peerRemoteClose called from wsPeer to report that it has closed func (n *P2PNetwork) peerRemoteClose(peer *wsPeer, reason disconnectReason) { - remotePeerID := peer.conn.(*wsPeerConnP2PImpl).stream.Conn().RemotePeer() + remotePeerID := peer.conn.(*wsPeerConnP2P).stream.Conn().RemotePeer() n.wsPeersLock.Lock() n.identityTracker.removeIdentity(peer) delete(n.wsPeers, remotePeerID) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 7cb35a0e82..8e30986cf1 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -345,11 +345,6 @@ func (s *mockService) AddrInfo() peer.AddrInfo { } } -func (s *mockService) DialNode(ctx context.Context, peer *peer.AddrInfo) error { - s.peers[peer.ID] = *peer - return nil -} - func (s *mockService) DialPeersUntilTargetCount(targetConnCount int) { } diff --git a/network/p2pPeer.go b/network/p2pPeer.go index a5065f01bb..9a0ce2699d 100644 --- a/network/p2pPeer.go +++ b/network/p2pPeer.go @@ -31,15 +31,15 @@ import ( mnet "github.com/multiformats/go-multiaddr/net" ) -type wsPeerConnP2PImpl struct { +type wsPeerConnP2P struct { stream network.Stream } -func (c *wsPeerConnP2PImpl) RemoteAddrString() string { +func (c *wsPeerConnP2P) RemoteAddrString() string { return c.stream.Conn().RemoteMultiaddr().String() } -func (c *wsPeerConnP2PImpl) NextReader() (int, io.Reader, error) { +func (c *wsPeerConnP2P) NextReader() (int, io.Reader, error) { // read length var lenbuf [4]byte _, err := io.ReadFull(c.stream, lenbuf[:]) @@ -54,7 +54,7 @@ func (c *wsPeerConnP2PImpl) NextReader() (int, io.Reader, error) { return websocket.BinaryMessage, io.LimitReader(c.stream, int64(msglen)), nil } -func (c *wsPeerConnP2PImpl) WriteMessage(_ int, buf []byte) error { +func (c *wsPeerConnP2P) WriteMessage(_ int, buf []byte) error { // simple message framing: // 1. write encoding of the length var lenbuf [4]byte @@ -69,13 +69,13 @@ func (c *wsPeerConnP2PImpl) WriteMessage(_ int, buf []byte) error { } // Do nothing for now since this doesn't actually close the connection just sends the close message -func (c *wsPeerConnP2PImpl) CloseWithMessage([]byte, time.Time) error { +func (c *wsPeerConnP2P) CloseWithMessage([]byte, time.Time) error { return nil } -func (c *wsPeerConnP2PImpl) SetReadLimit(int64) {} +func (c *wsPeerConnP2P) SetReadLimit(int64) {} -func (c *wsPeerConnP2PImpl) CloseWithoutFlush() error { +func (c *wsPeerConnP2P) CloseWithoutFlush() error { err := c.stream.Close() if err != nil && err != yamux.ErrStreamClosed && err != yamux.ErrSessionShutdown && err != yamux.ErrStreamReset { return err @@ -83,9 +83,9 @@ func (c *wsPeerConnP2PImpl) CloseWithoutFlush() error { return nil } -func (c *wsPeerConnP2PImpl) UnderlyingConn() net.Conn { return nil } +func (c *wsPeerConnP2P) UnderlyingConn() net.Conn { return nil } -func (c *wsPeerConnP2PImpl) RemoteAddr() net.Addr { +func (c *wsPeerConnP2P) RemoteAddr() net.Addr { netaddr, err := mnet.ToNetAddr(c.stream.Conn().RemoteMultiaddr()) if err != nil { logging.Base().Errorf("Error converting multiaddr to netaddr: %v", err) diff --git a/node/node.go b/node/node.go index b6118aadc0..04d2ced84c 100644 --- a/node/node.go +++ b/node/node.go @@ -393,10 +393,10 @@ func (node *AlgorandFullNode) Start() error { // Capabilities returns the node's capabilities for advertising to other nodes. func (node *AlgorandFullNode) Capabilities() []p2p.Capability { var caps []p2p.Capability - if node.config.Archival { + if node.config.Archival && node.config.IsGossipServer() { caps = append(caps, p2p.Archival) } - if node.config.StoresCatchpoints() { + if node.config.StoresCatchpoints() && node.config.IsGossipServer() { caps = append(caps, p2p.Catchpoints) } if node.config.EnableGossipService && node.config.IsGossipServer() { From 3b9f3e3de2bd9a5ad87b2b51624dce9f68b4f2cd Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:32:43 -0400 Subject: [PATCH 202/218] p2p: get rid of interface{} from PeerStore methods (#6101) --- network/p2p/capabilities_test.go | 4 +-- network/p2p/p2p.go | 7 ++--- network/p2p/peerstore/peerstore.go | 40 ++++++++++--------------- network/p2p/peerstore/peerstore_test.go | 35 +++++++++------------- network/p2pNetwork.go | 17 +++++++---- network/p2pNetwork_test.go | 2 +- network/phonebook/phonebook.go | 2 +- 7 files changed, 48 insertions(+), 59 deletions(-) diff --git a/network/p2p/capabilities_test.go b/network/p2p/capabilities_test.go index 5b41ed70d8..7057eca017 100644 --- a/network/p2p/capabilities_test.go +++ b/network/p2p/capabilities_test.go @@ -83,7 +83,7 @@ func setupDHTHosts(t *testing.T, numHosts int) []*dht.IpfsDHT { tmpdir := t.TempDir() pk, err := GetPrivKey(cfg, tmpdir) require.NoError(t, err) - ps, err := peerstore.NewPeerStore([]*peer.AddrInfo{}, "") + ps, err := peerstore.NewPeerStore(nil, "") require.NoError(t, err) h, err := libp2p.New( libp2p.ListenAddrStrings("/dns4/localhost/tcp/0"), @@ -134,7 +134,7 @@ func setupCapDiscovery(t *testing.T, numHosts int, numBootstrapPeers int) []*Cap tmpdir := t.TempDir() pk, err := GetPrivKey(cfg, tmpdir) require.NoError(t, err) - ps, err := peerstore.NewPeerStore([]*peer.AddrInfo{}, "") + ps, err := peerstore.NewPeerStore(nil, "") require.NoError(t, err) h, err := libp2p.New( libp2p.ListenAddrStrings("/dns4/localhost/tcp/0"), diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 2f8e00b911..3b467b0b27 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -176,7 +176,7 @@ func configureResourceManager(cfg config.Local) (network.ResourceManager, error) } // MakeService creates a P2P service instance -func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler, bootstrapPeers []*peer.AddrInfo) (*serviceImpl, error) { +func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler) (*serviceImpl, error) { sm := makeStreamManager(ctx, log, h, wsStreamHandler, cfg.EnableGossipService) h.Network().Notify(sm) @@ -238,7 +238,7 @@ func (s *serviceImpl) IDSigner() *PeerIDChallengeSigner { // DialPeersUntilTargetCount attempts to establish connections to the provided phonebook addresses func (s *serviceImpl) DialPeersUntilTargetCount(targetConnCount int) { ps := s.host.Peerstore().(*pstore.PeerStore) - peerIDs := ps.GetAddresses(targetConnCount, phonebook.PhoneBookEntryRelayRole) + addrInfos := ps.GetAddresses(targetConnCount, phonebook.PhoneBookEntryRelayRole) conns := s.host.Network().Conns() var numOutgoingConns int for _, conn := range conns { @@ -246,8 +246,7 @@ func (s *serviceImpl) DialPeersUntilTargetCount(targetConnCount int) { numOutgoingConns++ } } - for _, peerInfo := range peerIDs { - peerInfo := peerInfo.(*peer.AddrInfo) + for _, peerInfo := range addrInfos { // if we are at our target count stop trying to connect if numOutgoingConns >= targetConnCount { return diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go index 3eda0d3686..5ae9c6aa04 100644 --- a/network/p2p/peerstore/peerstore.go +++ b/network/p2p/peerstore/peerstore.go @@ -22,12 +22,13 @@ import ( "math/rand" "time" - "github.com/algorand/go-algorand/network/phonebook" - "github.com/algorand/go-deadlock" "github.com/libp2p/go-libp2p/core/peer" libp2p "github.com/libp2p/go-libp2p/core/peerstore" mempstore "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" "golang.org/x/exp/slices" + + "github.com/algorand/go-algorand/network/phonebook" + "github.com/algorand/go-deadlock" ) // when using GetAddresses with getAllAddresses, all the addresses will be retrieved, regardless @@ -76,14 +77,8 @@ func NewPeerStore(addrInfo []*peer.AddrInfo, network string) (*PeerStore, error) return nil, fmt.Errorf("cannot initialize a peerstore: %w", err) } - // initialize peerstore with addresses - peers := make([]interface{}, len(addrInfo)) - for i := 0; i < len(addrInfo); i++ { - peers[i] = addrInfo[i] - } - pstore := &PeerStore{peerStoreCAB: ps} - pstore.AddPersistentPeers(peers, network, phonebook.PhoneBookEntryRelayRole) + pstore.AddPersistentPeers(addrInfo, network, phonebook.PhoneBookEntryRelayRole) return pstore, nil } @@ -102,7 +97,7 @@ func MakePhonebook(connectionsRateLimitingCount uint, } // GetAddresses returns up to N addresses, but may return fewer -func (ps *PeerStore) GetAddresses(n int, role phonebook.PhoneBookEntryRoles) []interface{} { +func (ps *PeerStore) GetAddresses(n int, role phonebook.PhoneBookEntryRoles) []*peer.AddrInfo { return shuffleSelect(ps.filterRetryTime(time.Now(), role), n) } @@ -210,7 +205,7 @@ func (ps *PeerStore) UpdateConnectionTime(addrOrPeerID string, provisionalTime t } // ReplacePeerList replaces the peer list for the given networkName and role. -func (ps *PeerStore) ReplacePeerList(addressesThey []interface{}, networkName string, role phonebook.PhoneBookEntryRoles) { +func (ps *PeerStore) ReplacePeerList(addressesThey []*peer.AddrInfo, networkName string, role phonebook.PhoneBookEntryRoles) { // prepare a map of items we'd like to remove. removeItems := make(map[peer.ID]bool, 0) peerIDs := ps.Peers() @@ -226,8 +221,7 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []interface{}, networkName st } } - for _, addr := range addressesThey { - info := addr.(*peer.AddrInfo) + for _, info := range addressesThey { data, _ := ps.Get(info.ID, addressDataKey) if data != nil { // we already have this. @@ -255,17 +249,15 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []interface{}, networkName st // AddPersistentPeers stores addresses of peers which are persistent. // i.e. they won't be replaced by ReplacePeerList calls -func (ps *PeerStore) AddPersistentPeers(dnsAddresses []interface{}, networkName string, role phonebook.PhoneBookEntryRoles) { - for _, addr := range dnsAddresses { - info := addr.(*peer.AddrInfo) +func (ps *PeerStore) AddPersistentPeers(addrInfo []*peer.AddrInfo, networkName string, role phonebook.PhoneBookEntryRoles) { + for _, info := range addrInfo { data, _ := ps.Get(info.ID, addressDataKey) if data != nil { // we already have this. // Make sure the persistence field is set to true ad := data.(addressData) ad.persistent = true - _ = ps.Put(info.ID, addressDataKey, data) - + _ = ps.Put(info.ID, addressDataKey, ad) } else { // we don't have this item. add it. ps.AddAddrs(info.ID, info.Addrs, libp2p.PermanentAddrTTL) @@ -328,8 +320,8 @@ func (ps *PeerStore) popNElements(n int, peerID peer.ID) { _ = ps.Put(peerID, addressDataKey, ad) } -func (ps *PeerStore) filterRetryTime(t time.Time, role phonebook.PhoneBookEntryRoles) []interface{} { - o := make([]interface{}, 0, len(ps.Peers())) +func (ps *PeerStore) filterRetryTime(t time.Time, role phonebook.PhoneBookEntryRoles) []*peer.AddrInfo { + o := make([]*peer.AddrInfo, 0, len(ps.Peers())) for _, peerID := range ps.Peers() { data, _ := ps.Get(peerID, addressDataKey) if data != nil { @@ -344,11 +336,11 @@ func (ps *PeerStore) filterRetryTime(t time.Time, role phonebook.PhoneBookEntryR return o } -func shuffleSelect(set []interface{}, n int) []interface{} { +func shuffleSelect(set []*peer.AddrInfo, n int) []*peer.AddrInfo { if n >= len(set) || n == getAllAddresses { // return shuffled copy of everything out := slices.Clone(set) - shuffleStrings(out) + shuffleAddrInfos(out) return out } // Pick random indexes from the set @@ -361,13 +353,13 @@ func shuffleSelect(set []interface{}, n int) []interface{} { } } } - out := make([]interface{}, n) + out := make([]*peer.AddrInfo, n) for i, index := range indexSample { out[i] = set[index] } return out } -func shuffleStrings(set []interface{}) { +func shuffleAddrInfos(set []*peer.AddrInfo) { rand.Shuffle(len(set), func(i, j int) { set[i], set[j] = set[j], set[i] }) } diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go index e855013d76..d82b34595d 100644 --- a/network/p2p/peerstore/peerstore_test.go +++ b/network/p2p/peerstore/peerstore_test.go @@ -91,8 +91,7 @@ func TestPeerstore(t *testing.T) { func testPhonebookAll(t *testing.T, set []*peer.AddrInfo, ph *PeerStore) { actual := ph.GetAddresses(len(set), PhoneBookEntryRelayRole) - for _, got := range actual { - info := got.(*peer.AddrInfo) + for _, info := range actual { ok := false for _, known := range set { if info.ID == known.ID { @@ -101,13 +100,12 @@ func testPhonebookAll(t *testing.T, set []*peer.AddrInfo, ph *PeerStore) { } } if !ok { - t.Errorf("get returned junk %#v", got) + t.Errorf("get returned junk %#v", info) } } for _, known := range set { ok := false - for _, got := range actual { - info := got.(*peer.AddrInfo) + for _, info := range actual { if info.ID == known.ID { ok = true break @@ -128,8 +126,7 @@ func testPhonebookUniform(t *testing.T, set []*peer.AddrInfo, ph *PeerStore, get } for i := 0; i < uniformityTestLength; i++ { actual := ph.GetAddresses(getsize, PhoneBookEntryRelayRole) - for _, xa := range actual { - info := xa.(*peer.AddrInfo) + for _, info := range actual { if _, ok := counts[info.ID.String()]; ok { counts[info.ID.String()]++ } @@ -226,11 +223,11 @@ func TestMultiPhonebook(t *testing.T) { require.NoError(t, err) infoSet = append(infoSet, info) } - pha := make([]interface{}, 0) + pha := make([]*peer.AddrInfo, 0) for _, e := range infoSet[:5] { pha = append(pha, e) } - phb := make([]interface{}, 0) + phb := make([]*peer.AddrInfo, 0) for _, e := range infoSet[5:] { phb = append(phb, e) } @@ -252,7 +249,7 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) { info, err := peerInfoFromDomainPort("a:4041") require.NoError(t, err) - persistentPeers := []interface{}{info} + persistentPeers := []*peer.AddrInfo{info} set := []string{"b:4042", "c:4043", "d:4044", "e:4045", "f:4046", "g:4047", "h:4048", "i:4049", "j:4010"} infoSet := make([]*peer.AddrInfo, 0) for _, addr := range set { @@ -261,11 +258,11 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) { infoSet = append(infoSet, info) } - pha := make([]interface{}, 0) + pha := make([]*peer.AddrInfo, 0) for _, e := range infoSet[:5] { pha = append(pha, e) } - phb := make([]interface{}, 0) + phb := make([]*peer.AddrInfo, 0) for _, e := range infoSet[5:] { phb = append(phb, e) } @@ -279,10 +276,8 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) { testPhonebookAll(t, append(infoSet, info), ph) allAddresses := ph.GetAddresses(len(set)+len(persistentPeers), PhoneBookEntryRelayRole) for _, pp := range persistentPeers { - pp := pp.(*peer.AddrInfo) found := false for _, addr := range allAddresses { - addr := addr.(*peer.AddrInfo) if addr.ID == pp.ID { found = true break @@ -303,11 +298,11 @@ func TestMultiPhonebookDuplicateFiltering(t *testing.T) { infoSet = append(infoSet, info) } - pha := make([]interface{}, 0) + pha := make([]*peer.AddrInfo, 0) for _, e := range infoSet[:7] { pha = append(pha, e) } - phb := make([]interface{}, 0) + phb := make([]*peer.AddrInfo, 0) for _, e := range infoSet[3:] { phb = append(phb, e) } @@ -343,7 +338,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) { // Test the addresses are populated in the phonebook and a // time can be added to one of them - entries.ReplacePeerList([]interface{}{info1, info2}, "default", PhoneBookEntryRelayRole) + entries.ReplacePeerList([]*peer.AddrInfo{info1, info2}, "default", PhoneBookEntryRelayRole) addrInPhonebook, waitTime, provisionalTime := entries.GetConnectionWaitTime(string(info1.ID)) require.Equal(t, true, addrInPhonebook) require.Equal(t, time.Duration(0), waitTime) @@ -458,14 +453,14 @@ func TestPhonebookRoles(t *testing.T) { relaysSet := []string{"relay1:4040", "relay2:4041", "relay3:4042"} archiverSet := []string{"archiver1:1111", "archiver2:1112", "archiver3:1113"} - infoRelaySet := make([]interface{}, 0) + infoRelaySet := make([]*peer.AddrInfo, 0) for _, addr := range relaysSet { info, err := peerInfoFromDomainPort(addr) require.NoError(t, err) infoRelaySet = append(infoRelaySet, info) } - infoArchiverSet := make([]interface{}, 0) + infoArchiverSet := make([]*peer.AddrInfo, 0) for _, addr := range archiverSet { info, err := peerInfoFromDomainPort(addr) require.NoError(t, err) @@ -485,12 +480,10 @@ func TestPhonebookRoles(t *testing.T) { entries := ph.GetAddresses(l, role) if role == PhoneBookEntryRelayRole { for _, entry := range entries { - entry := entry.(*peer.AddrInfo) require.Contains(t, string(entry.ID), "relay") } } else if role == PhoneBookEntryArchiverRole { for _, entry := range entries { - entry := entry.(*peer.AddrInfo) require.Contains(t, string(entry.ID), "archiver") } } diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index ab53f72e01..f9dc04b785 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -261,15 +261,21 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo } log.Infof("P2P host created: peer ID %s addrs %s", h.ID(), h.Addrs()) - net.service, err = p2p.MakeService(net.ctx, log, cfg, h, la, net.wsStreamHandler, addrInfo) + net.service, err = p2p.MakeService(net.ctx, log, cfg, h, la, net.wsStreamHandler) if err != nil { return nil, err } + peerIDs := pstore.Peers() + addrInfos := make([]*peer.AddrInfo, 0, len(peerIDs)) + for _, peerID := range peerIDs { + addrInfo := pstore.PeerInfo(peerID) + addrInfos = append(addrInfos, &addrInfo) + } bootstrapper := &bootstrapper{ cfg: cfg, networkID: networkID, - phonebookPeers: addrInfo, + phonebookPeers: addrInfos, resolveController: dnsaddr.NewMultiaddrDNSResolveController(cfg.DNSSecurityTXTEnforced(), ""), log: net.log, } @@ -426,7 +432,7 @@ func (n *P2PNetwork) meshThreadInner() int { } peers := mergeP2PAddrInfoResolvedAddresses(dnsPeers, dhtPeers) - replace := make([]interface{}, 0, len(peers)) + replace := make([]*peer.AddrInfo, 0, len(peers)) for i := range peers { replace = append(replace, &peers[i]) } @@ -631,9 +637,8 @@ func (n *P2PNetwork) GetPeers(options ...PeerOption) []Peer { n.wsPeersLock.RUnlock() case PeersPhonebookRelays: const maxNodes = 100 - peerIDs := n.pstore.GetAddresses(maxNodes, phonebook.PhoneBookEntryRelayRole) - for _, peerInfo := range peerIDs { - peerInfo := peerInfo.(*peer.AddrInfo) + addrInfos := n.pstore.GetAddresses(maxNodes, phonebook.PhoneBookEntryRelayRole) + for _, peerInfo := range addrInfos { if peerCore, ok := addrInfoToWsPeerCore(n, peerInfo); ok { peers = append(peers, &peerCore) } diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 8e30986cf1..302aa76147 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -782,7 +782,7 @@ func TestP2PHTTPHandler(t *testing.T) { // zero clients allowed, rate limiting window (10s) is greater than queue deadline (1s) pstore, err := peerstore.MakePhonebook(0, 10*time.Second) require.NoError(t, err) - pstore.AddPersistentPeers([]interface{}{&peerInfoA}, "net", phonebook.PhoneBookEntryRelayRole) + pstore.AddPersistentPeers([]*peer.AddrInfo{&peerInfoA}, "net", phonebook.PhoneBookEntryRelayRole) httpClient, err = p2p.MakeHTTPClientWithRateLimit(&peerInfoA, pstore, 1*time.Second, 1) require.NoError(t, err) _, err = httpClient.Get("/test") diff --git a/network/phonebook/phonebook.go b/network/phonebook/phonebook.go index 634ca9c16c..b3aeafb0fa 100644 --- a/network/phonebook/phonebook.go +++ b/network/phonebook/phonebook.go @@ -204,7 +204,7 @@ func (e *phonebookImpl) AddPersistentPeers(dnsAddresses []string, networkName st // we already have this. // Make sure the persistence field is set to true pbData.persistent = true - + e.data[addr] = pbData } else { // we don't have this item. add it. e.data[addr] = makePhonebookEntryData(networkName, role, true) From 8e2e2e4f4f714e40d6156ed77cbd1b4b4763d11a Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 15 Aug 2024 13:33:04 -0400 Subject: [PATCH 203/218] Installer: add support for algoh to systemd installs (#6102) --- cmd/updater/systemd-setup-user.sh | 18 ++++++++++++++++++ cmd/updater/systemd-setup.sh | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/cmd/updater/systemd-setup-user.sh b/cmd/updater/systemd-setup-user.sh index fa17a1db2b..c5b145eff7 100755 --- a/cmd/updater/systemd-setup-user.sh +++ b/cmd/updater/systemd-setup-user.sh @@ -21,9 +21,27 @@ setup_user() { sed -e s,@@BINDIR@@,"$bindir", "${SCRIPTPATH}/algorand@.service.template-user" \ > "$homedir/.config/systemd/user/algorand@.service" + if [[ ${HOSTMODE} == true ]]; then + echo "[INFO] Hosted mode - replacing algod with algoh" + sed -i 's/algod/algoh/g' "$homedir/.config/systemd/user/algorand@.service" + fi + systemctl --user daemon-reload } +HOSTMODE=false +while getopts H opt; do + case $opt in + H) + HOSTMODE=true + ;; + ?) + echo "Invalid option: -${OPTARG}" + exit 1 + ;; + esac +done +shift $((OPTIND-1)) if [ "$#" != 1 ]; then echo "Usage: $0 username" diff --git a/cmd/updater/systemd-setup.sh b/cmd/updater/systemd-setup.sh index bad8745137..fc27fd209c 100755 --- a/cmd/updater/systemd-setup.sh +++ b/cmd/updater/systemd-setup.sh @@ -14,9 +14,28 @@ setup_root() { sed ${sedargs} "${SCRIPTPATH}/algorand@.service.template" \ > /lib/systemd/system/algorand@.service + if [[ ${HOSTMODE} == true ]]; then + echo "[INFO] Hosted mode - replacing algod with algoh" + sed -i 's/algod/algoh/g' /lib/systemd/system/algorand@.service + fi + systemctl daemon-reload } +HOSTMODE=false +while getopts H opt; do + case $opt in + H) + HOSTMODE=true + ;; + ?) + echo "Invalid option: -${OPTARG}" + exit 1 + ;; + esac +done +shift $((OPTIND-1)) + if [ "$#" != 2 ] && [ "$#" != 3 ]; then echo "Usage: $0 username group [bindir]" exit 1 From 499007787f522af16781b3f3e43a70b3516ea7e3 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:06:35 -0400 Subject: [PATCH 204/218] network: fix publicKeyIdentTracker data race in hybrid mode (#6110) --- network/netidentity.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/network/netidentity.go b/network/netidentity.go index 30755f0648..74f9b09e62 100644 --- a/network/netidentity.go +++ b/network/netidentity.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-deadlock" ) // netidentity.go implements functionality to participate in an "Identity Challenge Exchange" @@ -461,12 +462,14 @@ func (noopIdentityTracker) removeIdentity(p *wsPeer) {} // mapping from PublicKeys exchanged in identity challenges to a peer // this structure is not thread-safe; it is protected by wn.peersLock or p2p.wsPeersLock type publicKeyIdentTracker struct { + mu deadlock.Mutex peersByID map[crypto.PublicKey]*wsPeer } // NewIdentityTracker returns a new publicKeyIdentTracker func NewIdentityTracker() *publicKeyIdentTracker { return &publicKeyIdentTracker{ + mu: deadlock.Mutex{}, peersByID: make(map[crypto.PublicKey]*wsPeer), } } @@ -475,6 +478,8 @@ func NewIdentityTracker() *publicKeyIdentTracker { // returns false if it was unable to load the peer into the given identity // or true otherwise (if the peer was already there, or if it was added) func (t *publicKeyIdentTracker) setIdentity(p *wsPeer) bool { + t.mu.Lock() + defer t.mu.Unlock() existingPeer, exists := t.peersByID[p.identity] if !exists { // the identity is not occupied, so set it and return true @@ -489,6 +494,8 @@ func (t *publicKeyIdentTracker) setIdentity(p *wsPeer) bool { // removeIdentity removes the entry in the peersByID map if it exists // and is occupied by the given peer func (t *publicKeyIdentTracker) removeIdentity(p *wsPeer) { + t.mu.Lock() + defer t.mu.Unlock() if t.peersByID[p.identity] == p { delete(t.peersByID, p.identity) } From 8d678c311969979cb0777c3d2befff1fa69621df Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:36:50 -0400 Subject: [PATCH 205/218] config: use P2PHybridIncomingConnectionsLimit only for hybrid mode (#6103) --- cmd/algocfg/profileCommand.go | 4 +- cmd/algocfg/profileCommand_test.go | 9 +++-- config/config.go | 2 +- config/config_test.go | 34 +++++++++++------ config/localTemplate.go | 31 +++++++++------ config/local_defaults.go | 4 +- daemon/algod/server.go | 20 +++++----- installer/config.json.example | 4 +- netdeploy/remote/deployedNetwork.go | 4 +- netdeploy/remote/nodeConfig.go | 38 +++++++++---------- netdeploy/remote/nodecfg/nodeDir.go | 16 ++++---- network/README-P2P.md | 2 +- network/hybridNetwork.go | 3 +- network/hybridNetwork_test.go | 2 +- network/p2p/README.md | 2 +- network/p2p/p2p.go | 2 +- node/node_test.go | 2 +- test/testdata/configs/config-v34.json | 4 +- .../scenario1s-p2p/copy-node-configs.py | 4 +- 19 files changed, 103 insertions(+), 84 deletions(-) diff --git a/cmd/algocfg/profileCommand.go b/cmd/algocfg/profileCommand.go index 076cb65a64..c96a95d2b9 100644 --- a/cmd/algocfg/profileCommand.go +++ b/cmd/algocfg/profileCommand.go @@ -106,7 +106,7 @@ var ( // P2P config defaults cfg.EnableP2PHybridMode = true - cfg.P2PNetAddress = ":4190" + cfg.P2PHybridNetAddress = ":4190" cfg.EnableDHTProviders = true return cfg }, @@ -125,7 +125,7 @@ var ( // P2P config defaults cfg.EnableP2PHybridMode = true - cfg.P2PNetAddress = ":4190" + cfg.P2PHybridNetAddress = ":4190" cfg.EnableDHTProviders = true return cfg }, diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go index 8d7d95a26e..bdec2d6577 100644 --- a/cmd/algocfg/profileCommand_test.go +++ b/cmd/algocfg/profileCommand_test.go @@ -17,9 +17,10 @@ package main import ( - "github.com/algorand/go-algorand/config" "testing" + "github.com/algorand/go-algorand/config" + "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/test/partitiontest" @@ -80,7 +81,7 @@ func Test_getConfigForArg(t *testing.T) { require.Equal(t, config.PlaceholderPublicAddress, cfg.PublicAddress) require.True(t, cfg.EnableP2PHybridMode) - require.Equal(t, ":4190", cfg.P2PNetAddress) + require.Equal(t, ":4190", cfg.P2PHybridNetAddress) require.True(t, cfg.EnableDHTProviders) }) @@ -100,7 +101,7 @@ func Test_getConfigForArg(t *testing.T) { require.Equal(t, config.PlaceholderPublicAddress, cfg.PublicAddress) require.True(t, cfg.EnableP2PHybridMode) - require.Equal(t, ":4190", cfg.P2PNetAddress) + require.Equal(t, ":4190", cfg.P2PHybridNetAddress) require.True(t, cfg.EnableDHTProviders) }) @@ -121,7 +122,7 @@ func Test_getConfigForArg(t *testing.T) { require.Equal(t, "", cfg.PublicAddress) require.True(t, cfg.EnableP2PHybridMode) - require.Equal(t, "", cfg.P2PNetAddress) + require.Equal(t, "", cfg.P2PHybridNetAddress) require.True(t, cfg.EnableDHTProviders) }) } diff --git a/config/config.go b/config/config.go index 65d711cacc..07cd69cbda 100644 --- a/config/config.go +++ b/config/config.go @@ -176,7 +176,7 @@ func enrichNetworkingConfig(source Local) (Local, error) { } // In hybrid mode we want to prevent connections from the same node over both P2P and WS. // The only way it is supported at the moment is to use net identity challenge that is based on PublicAddress. - if (source.NetAddress != "" || source.P2PNetAddress != "") && source.EnableP2PHybridMode && source.PublicAddress == "" { + if (source.NetAddress != "" || source.P2PHybridNetAddress != "") && source.EnableP2PHybridMode && source.PublicAddress == "" { return source, errors.New("PublicAddress must be specified when EnableP2PHybridMode is set") } source.PublicAddress = strings.ToLower(source.PublicAddress) diff --git a/config/config_test.go b/config/config_test.go index 20338766c3..1b1c4c2753 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -153,7 +153,7 @@ func TestLocal_EnrichNetworkingConfig(t *testing.T) { require.ErrorContains(t, err, "PublicAddress must be specified when EnableP2PHybridMode is set") c1 = Local{ - P2PNetAddress: "test1", + P2PHybridNetAddress: "test1", EnableP2PHybridMode: true, } c2, err = enrichNetworkingConfig(c1) @@ -617,23 +617,27 @@ func TestLocal_IsGossipServer(t *testing.T) { require.False(t, cfg.IsGossipServer()) require.False(t, cfg.IsWsGossipServer()) require.False(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) cfg.NetAddress = ":4160" require.True(t, cfg.IsGossipServer()) require.True(t, cfg.IsWsGossipServer()) require.False(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) cfg.EnableGossipService = false // EnableGossipService does not matter require.True(t, cfg.IsGossipServer()) require.True(t, cfg.IsWsGossipServer()) require.False(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) cfg.EnableP2P = true cfg.NetAddress = ":4160" require.True(t, cfg.IsGossipServer()) require.False(t, cfg.IsWsGossipServer()) require.True(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) cfg.EnableP2P = false @@ -642,41 +646,47 @@ func TestLocal_IsGossipServer(t *testing.T) { require.True(t, cfg.IsGossipServer()) require.True(t, cfg.IsWsGossipServer()) require.False(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) cfg.EnableP2PHybridMode = true cfg.NetAddress = "" require.False(t, cfg.IsGossipServer()) require.False(t, cfg.IsWsGossipServer()) require.False(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) cfg.EnableP2PHybridMode = true - cfg.P2PNetAddress = ":4190" + cfg.P2PHybridNetAddress = ":4190" require.True(t, cfg.IsGossipServer()) require.False(t, cfg.IsWsGossipServer()) require.True(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) cfg.EnableP2PHybridMode = true cfg.NetAddress = ":4160" - cfg.P2PNetAddress = ":4190" + cfg.P2PHybridNetAddress = ":4190" require.True(t, cfg.IsGossipServer()) require.True(t, cfg.IsWsGossipServer()) require.True(t, cfg.IsP2PGossipServer()) + require.True(t, cfg.IsHybridServer()) cfg.EnableP2PHybridMode = true cfg.EnableP2P = true cfg.NetAddress = ":4160" - cfg.P2PNetAddress = ":4190" + cfg.P2PHybridNetAddress = ":4190" require.True(t, cfg.IsGossipServer()) require.True(t, cfg.IsWsGossipServer()) require.True(t, cfg.IsP2PGossipServer()) + require.True(t, cfg.IsHybridServer()) cfg.EnableP2PHybridMode = true cfg.EnableP2P = true cfg.NetAddress = ":4160" - cfg.P2PNetAddress = "" + cfg.P2PHybridNetAddress = "" require.True(t, cfg.IsGossipServer()) require.True(t, cfg.IsWsGossipServer()) require.False(t, cfg.IsP2PGossipServer()) + require.False(t, cfg.IsHybridServer()) } func TestLocal_RecalculateConnectionLimits(t *testing.T) { @@ -720,15 +730,15 @@ func TestLocal_RecalculateConnectionLimits(t *testing.T) { t.Parallel() c := Local{ - NetAddress: ":4160", - RestConnectionsSoftLimit: test.restSoftIn, - RestConnectionsHardLimit: test.restHardIn, - IncomingConnectionsLimit: test.incomingIn, - P2PIncomingConnectionsLimit: test.p2pIncomingIn, + NetAddress: ":4160", + RestConnectionsSoftLimit: test.restSoftIn, + RestConnectionsHardLimit: test.restHardIn, + IncomingConnectionsLimit: test.incomingIn, + P2PHybridIncomingConnectionsLimit: test.p2pIncomingIn, } if test.p2pIncomingIn > 0 { c.EnableP2PHybridMode = true - c.P2PNetAddress = ":4190" + c.P2PHybridNetAddress = ":4190" } requireFDs := test.reservedIn + test.restHardIn + uint64(test.incomingIn) + uint64(test.p2pIncomingIn) res := c.AdjustConnectionLimits(requireFDs, test.maxFDs) @@ -736,7 +746,7 @@ func TestLocal_RecalculateConnectionLimits(t *testing.T) { require.Equal(t, int(test.restSoftExp), int(c.RestConnectionsSoftLimit)) require.Equal(t, int(test.restHardExp), int(c.RestConnectionsHardLimit)) require.Equal(t, int(test.incomingExp), int(c.IncomingConnectionsLimit)) - require.Equal(t, int(test.p2pIncomingExp), int(c.P2PIncomingConnectionsLimit)) + require.Equal(t, int(test.p2pIncomingExp), int(c.P2PHybridIncomingConnectionsLimit)) }) } } diff --git a/config/localTemplate.go b/config/localTemplate.go index 9583a194cd..96a150a88b 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -134,7 +134,9 @@ type Local struct { // Estimating 1.5MB per incoming connection, 1.5MB*2400 = 3.6GB IncomingConnectionsLimit int `version[0]:"-1" version[1]:"10000" version[17]:"800" version[27]:"2400"` - P2PIncomingConnectionsLimit int `version[34]:"1200"` + // P2PHybridIncomingConnectionsLimit is used as IncomingConnectionsLimit for P2P connections in hybrid mode. + // For pure P2P nodes IncomingConnectionsLimit is used. + P2PHybridIncomingConnectionsLimit int `version[34]:"1200"` // BroadcastConnectionsLimit specifies the number of connections that // will receive broadcast (gossip) messages from this node. If the @@ -607,8 +609,8 @@ type Local struct { // Enabling this setting also requires PublicAddress to be set. EnableP2PHybridMode bool `version[34]:"false"` - // P2PNetAddress sets the listen address used for P2P networking, if hybrid mode is set. - P2PNetAddress string `version[34]:""` + // P2PHybridNetAddress sets the listen address used for P2P networking, if hybrid mode is set. + P2PHybridNetAddress string `version[34]:""` // EnableDHT will turn on the hash table for use with capabilities advertisement EnableDHTProviders bool `version[34]:"false"` @@ -742,16 +744,21 @@ func (cfg Local) IsGossipServer() bool { return cfg.IsWsGossipServer() || cfg.IsP2PGossipServer() } -// IsWsGossipServer returns true if a node configured to run a listening ws net +// IsWsGossipServer returns true if a node is configured to run a listening ws net func (cfg Local) IsWsGossipServer() bool { // 1. NetAddress is set and EnableP2P is not set // 2. NetAddress is set and EnableP2PHybridMode is set then EnableP2P is overridden by EnableP2PHybridMode return cfg.NetAddress != "" && (!cfg.EnableP2P || cfg.EnableP2PHybridMode) } -// IsP2PGossipServer returns true if a node configured to run a listening p2p net +// IsP2PGossipServer returns true if a node is configured to run a listening p2p net func (cfg Local) IsP2PGossipServer() bool { - return (cfg.EnableP2P && !cfg.EnableP2PHybridMode && cfg.NetAddress != "") || (cfg.EnableP2PHybridMode && cfg.P2PNetAddress != "") + return (cfg.EnableP2P && !cfg.EnableP2PHybridMode && cfg.NetAddress != "") || (cfg.EnableP2PHybridMode && cfg.P2PHybridNetAddress != "") +} + +// IsHybridServer returns true if a node configured to run a listening both ws and p2p networks +func (cfg Local) IsHybridServer() bool { + return cfg.NetAddress != "" && cfg.P2PHybridNetAddress != "" && cfg.EnableP2PHybridMode } // ensureAbsGenesisDir will convert a path to absolute, and will attempt to make a genesis directory there @@ -950,22 +957,22 @@ func (cfg *Local) AdjustConnectionLimits(requiredFDs, maxFDs uint64) bool { restDelta := diff + reservedRESTConns - cfg.RestConnectionsHardLimit cfg.RestConnectionsHardLimit = reservedRESTConns splitRatio := 1 - if cfg.IsWsGossipServer() && cfg.IsP2PGossipServer() { + if cfg.IsHybridServer() { // split the rest of the delta between ws and p2p evenly splitRatio = 2 } - if cfg.IsWsGossipServer() { + if cfg.IsWsGossipServer() || cfg.IsP2PGossipServer() { if cfg.IncomingConnectionsLimit > int(restDelta) { cfg.IncomingConnectionsLimit -= int(restDelta) / splitRatio } else { cfg.IncomingConnectionsLimit = 0 } } - if cfg.IsP2PGossipServer() { - if cfg.P2PIncomingConnectionsLimit > int(restDelta) { - cfg.P2PIncomingConnectionsLimit -= int(restDelta) / splitRatio + if cfg.IsHybridServer() { + if cfg.P2PHybridIncomingConnectionsLimit > int(restDelta) { + cfg.P2PHybridIncomingConnectionsLimit -= int(restDelta) / splitRatio } else { - cfg.P2PIncomingConnectionsLimit = 0 + cfg.P2PHybridIncomingConnectionsLimit = 0 } } } else { diff --git a/config/local_defaults.go b/config/local_defaults.go index 57457531be..b2007621d0 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -119,8 +119,8 @@ var defaultLocal = Local{ OptimizeAccountsDatabaseOnStartup: false, OutgoingMessageFilterBucketCount: 3, OutgoingMessageFilterBucketSize: 128, - P2PIncomingConnectionsLimit: 1200, - P2PNetAddress: "", + P2PHybridIncomingConnectionsLimit: 1200, + P2PHybridNetAddress: "", P2PPersistPeerID: false, P2PPrivateKeyLocation: "", ParticipationKeysRefreshInterval: 60000000000, diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 309fdc5799..6023bfab2e 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -153,16 +153,16 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes if ot.Overflowed { return errors.New("Initialize() overflowed when adding up ReservedHealthServiceConnections to the existing RLIMIT_NOFILE value; decrease RestConnectionsHardLimit") } - if cfg.IsWsGossipServer() { + if cfg.IsGossipServer() { fdRequired = ot.Add(fdRequired, uint64(cfg.IncomingConnectionsLimit)) if ot.Overflowed { return errors.New("Initialize() overflowed when adding up IncomingConnectionsLimit to the existing RLIMIT_NOFILE value; decrease IncomingConnectionsLimit") } } - if cfg.IsP2PGossipServer() { - fdRequired = ot.Add(fdRequired, uint64(cfg.P2PIncomingConnectionsLimit)) + if cfg.IsHybridServer() { + fdRequired = ot.Add(fdRequired, uint64(cfg.P2PHybridIncomingConnectionsLimit)) if ot.Overflowed { - return errors.New("Initialize() overflowed when adding up P2PIncomingConnectionsLimit to the existing RLIMIT_NOFILE value; decrease P2PIncomingConnectionsLimit") + return errors.New("Initialize() overflowed when adding up P2PHybridIncomingConnectionsLimit to the existing RLIMIT_NOFILE value; decrease P2PHybridIncomingConnectionsLimit") } } _, hard, fdErr := util.GetFdLimits() @@ -176,17 +176,17 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes // but try to keep cfg.ReservedFDs untouched by decreasing other limits if cfg.AdjustConnectionLimits(fdRequired, hard) { s.log.Warnf( - "Updated connection limits: RestConnectionsSoftLimit=%d, RestConnectionsHardLimit=%d, IncomingConnectionsLimit=%d, P2PIncomingConnectionsLimit=%d", + "Updated connection limits: RestConnectionsSoftLimit=%d, RestConnectionsHardLimit=%d, IncomingConnectionsLimit=%d, P2PHybridIncomingConnectionsLimit=%d", cfg.RestConnectionsSoftLimit, cfg.RestConnectionsHardLimit, cfg.IncomingConnectionsLimit, - cfg.P2PIncomingConnectionsLimit, + cfg.P2PHybridIncomingConnectionsLimit, ) - if cfg.IsWsGossipServer() && cfg.IncomingConnectionsLimit == 0 { - return errors.New("Initialize() failed to adjust connection limits") + if cfg.IsHybridServer() && cfg.P2PHybridIncomingConnectionsLimit == 0 { + return errors.New("Initialize() failed to adjust p2p hybrid connection limits") } - if cfg.IsP2PGossipServer() && cfg.P2PIncomingConnectionsLimit == 0 { - return errors.New("Initialize() failed to adjust p2p connection limits") + if cfg.IsGossipServer() && cfg.IncomingConnectionsLimit == 0 { + return errors.New("Initialize() failed to adjust connection limits") } } } diff --git a/installer/config.json.example b/installer/config.json.example index 3a9714bbfb..59ca9e3a29 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -98,8 +98,8 @@ "OptimizeAccountsDatabaseOnStartup": false, "OutgoingMessageFilterBucketCount": 3, "OutgoingMessageFilterBucketSize": 128, - "P2PIncomingConnectionsLimit": 1200, - "P2PNetAddress": "", + "P2PHybridIncomingConnectionsLimit": 1200, + "P2PHybridNetAddress": "", "P2PPersistPeerID": false, "P2PPrivateKeyLocation": "", "ParticipationKeysRefreshInterval": 60000000000, diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go index 26f25a0da6..2a02e77899 100644 --- a/netdeploy/remote/deployedNetwork.go +++ b/netdeploy/remote/deployedNetwork.go @@ -1009,8 +1009,8 @@ func createHostSpec(host HostConfig, template cloudHost) (hostSpec cloudHostSpec portList = append(portList, strconv.Itoa(port)) } } - if node.P2PNetAddress != "" { - port, err = extractPublicPort(node.P2PNetAddress) + if node.P2PHybridNetAddress != "" { + port, err = extractPublicPort(node.P2PHybridNetAddress) if err != nil { return } diff --git a/netdeploy/remote/nodeConfig.go b/netdeploy/remote/nodeConfig.go index bd4b63dac8..4025c47c1c 100644 --- a/netdeploy/remote/nodeConfig.go +++ b/netdeploy/remote/nodeConfig.go @@ -18,25 +18,25 @@ package remote // NodeConfig represents the configuration settings to apply to a single node running on a host type NodeConfig struct { - Name string `json:",omitempty"` - Wallets []NodeWalletData - NetAddress string `json:",omitempty"` - APIEndpoint string `json:",omitempty"` - APIToken string `json:",omitempty"` - AdminAPIToken string `json:",omitempty"` - EnableTelemetry bool // Needs to also be configured host-wide (assign logging host name) - TelemetryURI string `json:",omitempty"` // Needs to be HostConfig - EnableMetrics bool // Needs to also be configured host-wide (register DNS entry) - MetricsURI string `json:",omitempty"` - EnableService bool - CronTabSchedule string `json:",omitempty"` - EnableBlockStats bool - DashboardEndpoint string `json:",omitempty"` - DeadlockOverride int `json:",omitempty"` // -1 = Disable deadlock detection, 0 = Use Default for build, 1 = Enable - ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete - P2PBootstrap bool // True if this node should be a p2p bootstrap node and registered in DNS - P2PNetAddress string `json:",omitempty"` - PublicAddress bool + Name string `json:",omitempty"` + Wallets []NodeWalletData + NetAddress string `json:",omitempty"` + APIEndpoint string `json:",omitempty"` + APIToken string `json:",omitempty"` + AdminAPIToken string `json:",omitempty"` + EnableTelemetry bool // Needs to also be configured host-wide (assign logging host name) + TelemetryURI string `json:",omitempty"` // Needs to be HostConfig + EnableMetrics bool // Needs to also be configured host-wide (register DNS entry) + MetricsURI string `json:",omitempty"` + EnableService bool + CronTabSchedule string `json:",omitempty"` + EnableBlockStats bool + DashboardEndpoint string `json:",omitempty"` + DeadlockOverride int `json:",omitempty"` // -1 = Disable deadlock detection, 0 = Use Default for build, 1 = Enable + ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete + P2PBootstrap bool // True if this node should be a p2p bootstrap node and registered in DNS + P2PHybridNetAddress string `json:",omitempty"` + PublicAddress bool // NodeNameMatchRegex is tested against Name in generated configs and if matched the rest of the configs in this record are applied as a template NodeNameMatchRegex string `json:",omitempty"` diff --git a/netdeploy/remote/nodecfg/nodeDir.go b/netdeploy/remote/nodecfg/nodeDir.go index 304fa4c636..43417dca27 100644 --- a/netdeploy/remote/nodecfg/nodeDir.go +++ b/netdeploy/remote/nodecfg/nodeDir.go @@ -169,9 +169,9 @@ func (nd *nodeDir) configureNetAddress() (err error) { } } } - if nd.P2PNetAddress != "" { - fmt.Fprintf(os.Stdout, " - Assigning P2PNetAddress: %s\n", nd.P2PNetAddress) - nd.config.P2PNetAddress = nd.P2PNetAddress + if nd.P2PHybridNetAddress != "" { + fmt.Fprintf(os.Stdout, " - Assigning P2PHybridNetAddress: %s\n", nd.P2PHybridNetAddress) + nd.config.P2PHybridNetAddress = nd.P2PHybridNetAddress } err = nd.saveConfig() return @@ -210,21 +210,21 @@ func (nd *nodeDir) configureP2PDNSBootstrap(p2pBootstrap bool) error { } // ensure p2p config params set are what is expected: // - EnableP2P or EnableP2PHybridMode - // - NetAddress or P2PNetAddress is set + // - NetAddress or P2PHybridNetAddress is set // - EnableGossipService if !nd.config.EnableP2P && !nd.config.EnableP2PHybridMode { return errors.New("p2p bootstrap requires EnableP2P or EnableP2PHybridMode to be set") } - if nd.NetAddress == "" && nd.P2PNetAddress == "" { - return errors.New("p2p bootstrap requires NetAddress or P2PNetAddress to be set") + if nd.NetAddress == "" && nd.P2PHybridNetAddress == "" { + return errors.New("p2p bootstrap requires NetAddress or P2PHybridNetAddress to be set") } if !nd.config.EnableGossipService { return errors.New("p2p bootstrap requires EnableGossipService to be set") } netAddress := nd.NetAddress - if nd.P2PNetAddress != "" { - netAddress = nd.P2PNetAddress + if nd.P2PHybridNetAddress != "" { + netAddress = nd.P2PHybridNetAddress } key, err := p2p.GetPrivKey(config.Local{P2PPersistPeerID: true}, nd.dataDir) diff --git a/network/README-P2P.md b/network/README-P2P.md index c67bd53273..853b54ed2d 100644 --- a/network/README-P2P.md +++ b/network/README-P2P.md @@ -8,7 +8,7 @@ transport: lip2p-managed connections and HTTP + WebSocket, respectively. `P2PNetwork` and `WsNetwork` require `config.NetAddress` to be set in order to start a server. In addition, `HybridNetwork` is an aggregate of `P2PNetwork` and `WsNetwork` allowing a node -to interact over both networks. In the case of hybrid operation, both `config.P2PNetAddress` and +to interact over both networks. In the case of hybrid operation, both `config.P2PHybridNetAddress` and `config.NetAddress` are used. ## General design diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go index 7d5814ff39..85621260a9 100644 --- a/network/hybridNetwork.go +++ b/network/hybridNetwork.go @@ -41,7 +41,8 @@ type HybridP2PNetwork struct { func NewHybridP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, nodeInfo NodeInfo) (*HybridP2PNetwork, error) { // supply alternate NetAddress for P2P network p2pcfg := cfg - p2pcfg.NetAddress = cfg.P2PNetAddress + p2pcfg.NetAddress = cfg.P2PHybridNetAddress + p2pcfg.IncomingConnectionsLimit = cfg.P2PHybridIncomingConnectionsLimit identityTracker := NewIdentityTracker() p2pnet, err := NewP2PNetwork(log, p2pcfg, datadir, phonebookAddresses, genesisID, networkID, nodeInfo, &identityOpts{tracker: identityTracker}) if err != nil { diff --git a/network/hybridNetwork_test.go b/network/hybridNetwork_test.go index 842bb10b15..4e1392a2d0 100644 --- a/network/hybridNetwork_test.go +++ b/network/hybridNetwork_test.go @@ -64,7 +64,7 @@ func TestHybridNetwork_DuplicateConn(t *testing.T) { // make it net address and restart the node relayCfg.NetAddress = addr relayCfg.PublicAddress = addr - relayCfg.P2PNetAddress = "127.0.0.1:0" + relayCfg.P2PHybridNetAddress = "127.0.0.1:0" netA, err = NewHybridP2PNetwork(log.With("node", "netA"), relayCfg, p2pKeyDir, nil, genesisID, "net", &nopeNodeInfo{}) require.NoError(t, err) diff --git a/network/p2p/README.md b/network/p2p/README.md index b95e5be32f..e2d3333caf 100644 --- a/network/p2p/README.md +++ b/network/p2p/README.md @@ -72,7 +72,7 @@ The underlying libp2p implementation is abstracted as `p2p.Service` and is initi ### Connection limiting -libp2p's `ResourceManager` is used to limit the number of connections up to `cfg.P2PIncomingConnectionsLimit`. +libp2p's `ResourceManager` is used to limit the number of connections up to `cfg.IncomingConnectionsLimit`. ### DHT and capabilities diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 3b467b0b27..270dfb7476 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -166,7 +166,7 @@ func configureResourceManager(cfg config.Local) (network.ResourceManager, error) limitConfig := rcmgr.PartialLimitConfig{ System: rcmgr.ResourceLimits{ - Conns: rcmgr.LimitVal(cfg.P2PIncomingConnectionsLimit), + Conns: rcmgr.LimitVal(cfg.IncomingConnectionsLimit), }, // Everything else is default. The exact values will come from `scaledDefaultLimits` above. } diff --git a/node/node_test.go b/node/node_test.go index df72a699bb..6b991751cb 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -880,7 +880,7 @@ func TestNodeHybridTopology(t *testing.T) { ni.p2pID, err = p2p.PeerIDFromPublicKey(privKey.GetPublic()) require.NoError(t, err) - cfg.P2PNetAddress = ni.p2pNetAddr() + cfg.P2PHybridNetAddress = ni.p2pNetAddr() return ni, cfg } diff --git a/test/testdata/configs/config-v34.json b/test/testdata/configs/config-v34.json index 3a9714bbfb..59ca9e3a29 100644 --- a/test/testdata/configs/config-v34.json +++ b/test/testdata/configs/config-v34.json @@ -98,8 +98,8 @@ "OptimizeAccountsDatabaseOnStartup": false, "OutgoingMessageFilterBucketCount": 3, "OutgoingMessageFilterBucketSize": 128, - "P2PIncomingConnectionsLimit": 1200, - "P2PNetAddress": "", + "P2PHybridIncomingConnectionsLimit": 1200, + "P2PHybridNetAddress": "", "P2PPersistPeerID": false, "P2PPrivateKeyLocation": "", "ParticipationKeysRefreshInterval": 60000000000, diff --git a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py index 12da86f348..93595698f2 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py +++ b/test/testdata/deployednettemplates/recipes/scenario1s-p2p/copy-node-configs.py @@ -65,7 +65,7 @@ def make_hybrid_p2p_net(*args): altconfig["ConfigJSONOverride"] = json.dumps(override_json) if net_address: # relay, set public address altconfig["P2PBootstrap"] = True - altconfig["P2PNetAddress"] = "{{NetworkPort2}}" + altconfig["P2PHybridNetAddress"] = "{{NetworkPort2}}" altconfig["PublicAddress"] = True altconfig['FractionApply'] = 0.5 @@ -99,7 +99,7 @@ def make_hybrid_ws_net(*args): altconfig["ConfigJSONOverride"] = json.dumps(override_json) if net_address: # relay, set public address altconfig["P2PBootstrap"] = True - altconfig["P2PNetAddress"] = "{{NetworkPort2}}" + altconfig["P2PHybridNetAddress"] = "{{NetworkPort2}}" altconfig["PublicAddress"] = True altconfig['FractionApply'] = 0.5 From e14fea6b350b4141eb674a23fe87259b74df8d2b Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:05:33 -0400 Subject: [PATCH 206/218] metrics: collect total netdev sent/received bytes (#6108) --- config/localTemplate.go | 3 + config/local_defaults.go | 1 + daemon/algod/server.go | 4 + go.mod | 6 +- go.sum | 14 +- installer/config.json.example | 1 + test/testdata/configs/config-v34.json | 1 + .../recipes/scenario1s/genesis.json | 2 +- .../recipes/scenario1s/net.json | 188 ++++++++++++------ .../recipes/scenario1s/node.json | 4 +- .../recipes/scenario1s/nonPartNode.json | 2 +- .../recipes/scenario1s/relay.json | 2 +- tools/block-generator/go.mod | 7 +- tools/block-generator/go.sum | 14 +- util/metrics/netdev_common.go | 88 ++++++++ util/metrics/netdev_darwin.go | 112 +++++++++++ util/metrics/netdev_linux.go | 66 ++++++ util/metrics/netdev_noop.go | 24 +++ util/metrics/registry.go | 3 + 19 files changed, 475 insertions(+), 67 deletions(-) create mode 100644 util/metrics/netdev_common.go create mode 100644 util/metrics/netdev_darwin.go create mode 100644 util/metrics/netdev_linux.go create mode 100644 util/metrics/netdev_noop.go diff --git a/config/localTemplate.go b/config/localTemplate.go index 96a150a88b..c03461c371 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -357,6 +357,9 @@ type Local struct { // EnableRuntimeMetrics exposes Go runtime metrics in /metrics and via node_exporter. EnableRuntimeMetrics bool `version[22]:"false"` + // EnableNetDevMetrics exposes network interface total bytes sent/received metrics in /metrics + EnableNetDevMetrics bool `version[34]:"false"` + // TelemetryToLog configures whether to record messages to node.log that are normally only sent to remote event monitoring. TelemetryToLog bool `version[5]:"true"` diff --git a/config/local_defaults.go b/config/local_defaults.go index b2007621d0..3315dadf2b 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -73,6 +73,7 @@ var defaultLocal = Local{ EnableIncomingMessageFilter: false, EnableLedgerService: false, EnableMetricReporting: false, + EnableNetDevMetrics: false, EnableOutgoingNetworkMessageFiltering: true, EnableP2P: false, EnableP2PHybridMode: false, diff --git a/daemon/algod/server.go b/daemon/algod/server.go index 6023bfab2e..a823ee0001 100644 --- a/daemon/algod/server.go +++ b/daemon/algod/server.go @@ -330,6 +330,10 @@ func (s *Server) Start() { metrics.DefaultRegistry().Register(metrics.NewRuntimeMetrics()) } + if cfg.EnableNetDevMetrics { + metrics.DefaultRegistry().Register(metrics.NetDevMetrics) + } + if cfg.EnableMetricReporting { if err := s.metricCollector.Start(context.Background()); err != nil { // log this error diff --git a/go.mod b/go.mod index e078ec73fb..a9056a976f 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/ipfs/go-log v1.0.5 github.com/ipfs/go-log/v2 v2.5.1 github.com/jmoiron/sqlx v1.2.0 + github.com/jsimonetti/rtnetlink v1.4.2 github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c github.com/labstack/echo/v4 v4.9.1 github.com/libp2p/go-libp2p v0.33.2 @@ -52,7 +53,7 @@ require ( golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 + golang.org/x/sys v0.20.0 golang.org/x/text v0.14.0 gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009 pgregory.net/rapid v0.6.2 @@ -109,6 +110,7 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/koron/go-ssdp v0.0.4 // indirect @@ -129,6 +131,8 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // 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 diff --git a/go.sum b/go.sum index 66924d7486..cec02bb2af 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas= github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= 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= @@ -304,6 +306,10 @@ github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= +github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -406,6 +412,10 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp 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/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 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= @@ -763,8 +773,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= diff --git a/installer/config.json.example b/installer/config.json.example index 59ca9e3a29..db4420ed9d 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -52,6 +52,7 @@ "EnableIncomingMessageFilter": false, "EnableLedgerService": false, "EnableMetricReporting": false, + "EnableNetDevMetrics": false, "EnableOutgoingNetworkMessageFiltering": true, "EnableP2P": false, "EnableP2PHybridMode": false, diff --git a/test/testdata/configs/config-v34.json b/test/testdata/configs/config-v34.json index 59ca9e3a29..db4420ed9d 100644 --- a/test/testdata/configs/config-v34.json +++ b/test/testdata/configs/config-v34.json @@ -52,6 +52,7 @@ "EnableIncomingMessageFilter": false, "EnableLedgerService": false, "EnableMetricReporting": false, + "EnableNetDevMetrics": false, "EnableOutgoingNetworkMessageFiltering": true, "EnableP2P": false, "EnableP2PHybridMode": false, diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/genesis.json b/test/testdata/deployednettemplates/recipes/scenario1s/genesis.json index 31da8b8c28..29fad78137 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/genesis.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/genesis.json @@ -3,7 +3,7 @@ "VersionModifier": "", "ConsensusProtocol": "future", "FirstPartKeyRound": 0, - "LastPartKeyRound": 22000, + "LastPartKeyRound": 5000, "PartKeyDilution": 0, "Wallets": [ { diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/net.json b/test/testdata/deployednettemplates/recipes/scenario1s/net.json index a93573426d..abd624d2d5 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/net.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/net.json @@ -17,7 +17,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -38,7 +40,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -59,7 +63,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -80,7 +86,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -101,7 +109,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -122,7 +132,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -143,7 +155,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -164,7 +178,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -188,7 +204,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -206,13 +224,15 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": false, + "EnableTelemetry": true, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": false, + "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -236,7 +256,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -260,7 +282,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -284,7 +308,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -302,13 +328,15 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": false, + "EnableTelemetry": true, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": false, + "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -332,7 +360,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -356,7 +386,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -380,7 +412,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -404,7 +438,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -428,7 +464,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -452,7 +490,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -476,7 +516,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -500,7 +542,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -524,7 +568,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -542,13 +588,15 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": true, + "EnableTelemetry": false, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": true, + "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -566,13 +614,15 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": true, + "EnableTelemetry": false, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": true, + "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -590,13 +640,15 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": false, + "EnableTelemetry": true, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": false, + "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}" + "EnableBlockStats": true, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -620,7 +672,9 @@ "MetricsURI": "{{MetricsURI}}", "EnableService": false, "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -638,13 +692,15 @@ ], "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "EnableTelemetry": true, + "EnableTelemetry": false, "TelemetryURI": "{{TelemetryURI}}", - "EnableMetrics": true, + "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", "EnableService": false, - "EnableBlockStats": true, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }" + "EnableBlockStats": false, + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -666,7 +722,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -688,7 +746,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -710,7 +770,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -732,7 +794,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -754,7 +818,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -776,7 +842,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -798,7 +866,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -820,7 +890,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -842,7 +914,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] }, @@ -864,7 +938,9 @@ "EnableMetrics": false, "EnableService": false, "EnableBlockStats": false, - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }", + "P2PBootstrap": false, + "PublicAddress": false } ] } diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/node.json b/test/testdata/deployednettemplates/recipes/scenario1s/node.json index 3b4cb78771..7a0266ef0a 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/node.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/node.json @@ -6,7 +6,7 @@ "TelemetryURI": "{{TelemetryURI}}", "EnableMetrics": false, "MetricsURI": "{{MetricsURI}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true}", + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}", "AltConfigs": [ { "APIEndpoint": "{{APIEndpoint}}", @@ -16,7 +16,7 @@ "TelemetryURI": "{{TelemetryURI}}", "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true }", + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"CadaverSizeTarget\": 0,\"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true }", "FractionApply": 0.2 } ] diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json b/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json index 3f1245f7dd..fd5214a582 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/nonPartNode.json @@ -1,5 +1,5 @@ { "APIEndpoint": "{{APIEndpoint}}", "APIToken": "{{APIToken}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true }" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0, \"EnableProfiler\": true, \"EnableExperimentalAPI\": true, \"EnableNetDevMetrics\": true }" } diff --git a/test/testdata/deployednettemplates/recipes/scenario1s/relay.json b/test/testdata/deployednettemplates/recipes/scenario1s/relay.json index 327cf0b188..41559d3d55 100644 --- a/test/testdata/deployednettemplates/recipes/scenario1s/relay.json +++ b/test/testdata/deployednettemplates/recipes/scenario1s/relay.json @@ -8,5 +8,5 @@ "TelemetryURI": "{{TelemetryURI}}", "EnableMetrics": true, "MetricsURI": "{{MetricsURI}}", - "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true}" + "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"EnableRuntimeMetrics\": true, \"EnableExperimentalAPI\": true, \"EnableAccountUpdatesStats\": true, \"EnableNetDevMetrics\": true}" } diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index 9baf9fafef..4f8b21613a 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -55,6 +55,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/google/uuid v1.4.0 // indirect @@ -77,6 +78,8 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/jsimonetti/rtnetlink v1.4.2 // indirect github.com/klauspost/compress v1.17.6 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/koron/go-ssdp v0.0.4 // indirect @@ -100,6 +103,8 @@ require ( github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.16 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect github.com/miekg/dns v1.1.58 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -152,7 +157,7 @@ require ( golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.18.0 // indirect gonum.org/v1/gonum v0.13.0 // indirect diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum index 5ed96a265b..b627db1073 100644 --- a/tools/block-generator/go.sum +++ b/tools/block-generator/go.sum @@ -57,6 +57,8 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas= github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= 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= @@ -279,6 +281,10 @@ github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2 github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= +github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -366,6 +372,10 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp 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/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 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= @@ -712,8 +722,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/util/metrics/netdev_common.go b/util/metrics/netdev_common.go new file mode 100644 index 0000000000..3da28aa998 --- /dev/null +++ b/util/metrics/netdev_common.go @@ -0,0 +1,88 @@ +// Copyright (C) 2019-2024 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 metrics + +import ( + "fmt" + "strconv" + "strings" +) + +type netDevStats struct { + bytesReceived uint64 + bytesSent uint64 + iface string +} + +type netDevGatherer struct { +} + +func writeUint64MetricCounterHeader(buf *strings.Builder, name string, desc string) { + buf.WriteString("# HELP ") + buf.WriteString(name) + buf.WriteString(" ") + buf.WriteString(desc) + buf.WriteString("\n# TYPE ") + buf.WriteString(name) + buf.WriteString(" counter\n") +} + +func writeUint64MetricValue(buf *strings.Builder, name string, labels string, value uint64) { + buf.WriteString(name) + if len(labels) > 0 { + buf.WriteString("{" + labels + "}") + } + buf.WriteString(" ") + buf.WriteString(strconv.FormatUint(value, 10)) + buf.WriteString("\n") +} + +// WriteMetric writes the netdev metrics to the provided buffer. +func (pg netDevGatherer) WriteMetric(buf *strings.Builder, parentLabels string) { + nds, err := getNetDevStats() + if err != nil { + return + } + var sep string + if len(parentLabels) > 0 { + sep = "," + } + + writeUint64MetricCounterHeader(buf, "algod_netdev_received_bytes", "Bytes received") + for _, nd := range nds { + labels := fmt.Sprintf("iface=\"%s\"%s%s", nd.iface, sep, parentLabels) + writeUint64MetricValue(buf, "algod_netdev_received_bytes", labels, nd.bytesReceived) + } + + writeUint64MetricCounterHeader(buf, "algod_netdev_sent_bytes", "Bytes sent") + for _, nd := range nds { + labels := fmt.Sprintf("iface=\"%s\"%s%s", nd.iface, sep, parentLabels) + writeUint64MetricValue(buf, "algod_netdev_sent_bytes", labels, nd.bytesSent) + } +} + +// AddMetric writes the netdev metrics to the provided map. +func (pg netDevGatherer) AddMetric(values map[string]float64) { + nds, err := getNetDevStats() + if err != nil { + return + } + for _, nd := range nds { + values[sanitizeTelemetryName("algod_netdev_received_bytes_"+nd.iface)] = float64(nd.bytesReceived) + values[sanitizeTelemetryName("algod_netdev_sent_bytes_"+nd.iface)] = float64(nd.bytesSent) + } +} diff --git a/util/metrics/netdev_darwin.go b/util/metrics/netdev_darwin.go new file mode 100644 index 0000000000..fd7b3cda9f --- /dev/null +++ b/util/metrics/netdev_darwin.go @@ -0,0 +1,112 @@ +// Copyright (C) 2019-2024 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 . + +//go:build darwin + +package metrics + +import ( + "bytes" + "encoding/binary" + "net" + + "golang.org/x/sys/unix" +) + +// getNetDevStats returns network device statistics. +func getNetDevStats() ([]netDevStats, error) { + nds := []netDevStats{} + + ifs, err := net.Interfaces() + if err != nil { + return nil, err + } + + for _, iface := range ifs { + ifaceData, err := getIfaceData(iface.Index) + if err != nil { + continue + } + if ifaceData.Data.Ibytes == 0 && ifaceData.Data.Obytes == 0 { + // skip interfaces with no traffic + continue + } + + nds = append(nds, netDevStats{ + bytesReceived: ifaceData.Data.Ibytes, + bytesSent: ifaceData.Data.Obytes, + iface: iface.Name, + }) + } + + return nds, nil +} + +// getIfaceData and ifMsghdr2 are copied node_exporter's collector/netdev_darwin.go +// Not sure what is the origin of this code (it also appears in few other golang projects), +// but it is licensed under Apache 2.0. +func getIfaceData(index int) (*ifMsghdr2, error) { + var data ifMsghdr2 + rawData, err := unix.SysctlRaw("net", unix.AF_ROUTE, 0, 0, unix.NET_RT_IFLIST2, index) + if err != nil { + return nil, err + } + err = binary.Read(bytes.NewReader(rawData), binary.LittleEndian, &data) + return &data, err +} + +type ifMsghdr2 struct { + Msglen uint16 + Version uint8 + Type uint8 + Addrs int32 + Flags int32 + Index uint16 + _ [2]byte + SndLen int32 + SndMaxlen int32 + SndDrops int32 + Timer int32 + Data ifData64 +} + +type ifData64 struct { + Type uint8 + Typelen uint8 + Physical uint8 + Addrlen uint8 + Hdrlen uint8 + Recvquota uint8 + Xmitquota uint8 + Unused1 uint8 + Mtu uint32 + Metric uint32 + Baudrate uint64 + Ipackets uint64 + Ierrors uint64 + Opackets uint64 + Oerrors uint64 + Collisions uint64 + Ibytes uint64 + Obytes uint64 + Imcasts uint64 + Omcasts uint64 + Iqdrops uint64 + Noproto uint64 + Recvtiming uint32 + Xmittiming uint32 + Lastchange unix.Timeval32 +} diff --git a/util/metrics/netdev_linux.go b/util/metrics/netdev_linux.go new file mode 100644 index 0000000000..7c6db07ae4 --- /dev/null +++ b/util/metrics/netdev_linux.go @@ -0,0 +1,66 @@ +// Copyright (C) 2019-2024 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 . + +//go:build linux + +package metrics + +import "github.com/jsimonetti/rtnetlink" + +func getNetDevStats() ([]netDevStats, error) { + nds := []netDevStats{} + + conn, err := rtnetlink.Dial(nil) + if err != nil { + return nil, err + } + + defer conn.Close() + links, err := conn.Link.List() + if err != nil { + return nil, err + } + + for _, msg := range links { + if msg.Attributes == nil { + continue + } + name := msg.Attributes.Name + stats := msg.Attributes.Stats64 + if stats != nil { + if stats.RXBytes == 0 && stats.TXBytes == 0 { + // skip interfaces with no traffic + continue + } + nds = append(nds, netDevStats{ + bytesReceived: stats.RXBytes, + bytesSent: stats.TXBytes, + iface: name, + }) + } else if stats32 := msg.Attributes.Stats; stats32 != nil { + if stats32.RXBytes == 0 && stats32.TXBytes == 0 { + // skip interfaces with no traffic + continue + } + nds = append(nds, netDevStats{ + bytesReceived: uint64(stats32.RXBytes), + bytesSent: uint64(stats32.TXBytes), + iface: name, + }) + } + } + return nds, nil +} diff --git a/util/metrics/netdev_noop.go b/util/metrics/netdev_noop.go new file mode 100644 index 0000000000..3d2a1a1851 --- /dev/null +++ b/util/metrics/netdev_noop.go @@ -0,0 +1,24 @@ +// Copyright (C) 2019-2024 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 . + +//go:build !linux && !darwin + +package metrics + +// getNetDevStats returns network device statistics. +func getNetDevStats() ([]netDevStats, error) { + return nil, nil +} diff --git a/util/metrics/registry.go b/util/metrics/registry.go index d525bc1833..f6d7849401 100644 --- a/util/metrics/registry.go +++ b/util/metrics/registry.go @@ -43,6 +43,9 @@ var PrometheusDefaultMetrics = defaultPrometheusGatherer{} // OpencensusDefaultMetrics is the default prometheus gatherer implementing the Metric interface var OpencensusDefaultMetrics = defaultOpencensusGatherer{} +// NetDevMetrics is a netdev gatherer implementing the Metric interface +var NetDevMetrics = netDevGatherer{} + func init() { defaultRegistry = MakeRegistry() } From 9d5c4cd77849a5df5f633bee30a4b68247912a71 Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Thu, 29 Aug 2024 14:47:05 -0400 Subject: [PATCH 207/218] REST API: Fix `LedgerStateDelta` JSON encoding (#6106) Co-authored-by: Ashy5000 --- daemon/algod/api/client/restClient.go | 23 +++++++++--- daemon/algod/api/server/v2/handlers.go | 32 +++++++++++++--- data/transactions/transaction.go | 6 +-- libgoal/libgoal.go | 5 ++- test/e2e-go/features/devmode/devmode_test.go | 27 +++++++++++--- .../features/followernode/syncDeltas_test.go | 37 ++++++++++++++++++- 6 files changed, 107 insertions(+), 23 deletions(-) diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 33b8da8ee1..a3dc4b769e 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -30,11 +30,14 @@ import ( "github.com/google/go-querystring/query" "github.com/algorand/go-algorand/crypto" + 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/daemon/algod/api/spec/common" "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/eval" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" ) @@ -229,7 +232,7 @@ func (client RestClient) submitForm( } if decodeJSON { - dec := json.NewDecoder(resp.Body) + dec := protocol.NewJSONDecoder(resp.Body) return dec.Decode(&response) } @@ -559,7 +562,9 @@ func (client RestClient) SendRawTransactionGroup(txgroup []transactions.SignedTx } // Block gets the block info for the given round -func (client RestClient) Block(round uint64) (response model.BlockResponse, err error) { +func (client RestClient) Block(round uint64) (response v2.BlockResponseJSON, err error) { + // Note: this endpoint gets the Block as JSON, meaning some string fields with non-UTF-8 data will lose + // information. Msgpack should be used instead if this becomes a problem. err = client.get(&response, fmt.Sprintf("/v2/blocks/%d", round), nil) return } @@ -767,19 +772,27 @@ func (client RestClient) GetSyncRound() (response model.GetSyncRoundResponse, er } // GetLedgerStateDelta retrieves the ledger state delta for the round -func (client RestClient) GetLedgerStateDelta(round uint64) (response model.LedgerStateDeltaResponse, err error) { +func (client RestClient) GetLedgerStateDelta(round uint64) (response ledgercore.StateDelta, err error) { + // Note: this endpoint gets the StateDelta as JSON, meaning some string fields with non-UTF-8 data will lose + // information. Msgpack should be used instead if this becomes a problem. err = client.get(&response, fmt.Sprintf("/v2/deltas/%d", round), nil) return } // GetLedgerStateDeltaForTransactionGroup retrieves the ledger state delta for the txn group specified by the id -func (client RestClient) GetLedgerStateDeltaForTransactionGroup(id string) (response model.LedgerStateDeltaForTransactionGroupResponse, err error) { +func (client RestClient) GetLedgerStateDeltaForTransactionGroup(id string) (response eval.StateDeltaSubset, err error) { + // Note: this endpoint gets the StateDelta as JSON, meaning some string fields with non-UTF-8 data will lose + // information. Msgpack should be used instead if this becomes a problem. err = client.get(&response, fmt.Sprintf("/v2/deltas/txn/group/%s", id), nil) return } // GetTransactionGroupLedgerStateDeltasForRound retrieves the ledger state deltas for the txn groups in the specified round -func (client RestClient) GetTransactionGroupLedgerStateDeltasForRound(round uint64) (response model.TransactionGroupLedgerStateDeltasForRoundResponse, err error) { +func (client RestClient) GetTransactionGroupLedgerStateDeltasForRound(round uint64) (response struct { + Deltas []eval.TxnGroupDeltaWithIds +}, err error) { + // Note: this endpoint gets the StateDelta as JSON, meaning some string fields with non-UTF-8 data will lose + // information. Msgpack should be used instead if this becomes a problem. err = client.get(&response, fmt.Sprintf("/v2/deltas/%d/txn/group", round), nil) return } diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index b638825ec4..9c5da32b3d 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -673,6 +673,11 @@ func (v2 *Handlers) AccountApplicationInformation(ctx echo.Context, address stri return ctx.JSON(http.StatusOK, response) } +// BlockResponseJSON is used to embed the block in JSON responses. +type BlockResponseJSON struct { + Block bookkeeping.Block `codec:"block"` +} + // GetBlock gets the block for the given round. // (GET /v2/blocks/{round}) func (v2 *Handlers) GetBlock(ctx echo.Context, round uint64, params model.GetBlockParams) error { @@ -709,9 +714,7 @@ func (v2 *Handlers) GetBlock(ctx echo.Context, round uint64, params model.GetBlo } // Encoding wasn't working well without embedding "real" objects. - response := struct { - Block bookkeeping.Block `codec:"block"` - }{ + response := BlockResponseJSON{ Block: block, } @@ -839,7 +842,7 @@ func (v2 *Handlers) GetBlockHash(ctx echo.Context, round uint64) error { // (GET /v2/blocks/{round}/transactions/{txid}/proof) func (v2 *Handlers) GetTransactionProof(ctx echo.Context, round uint64, txid string, params model.GetTransactionProofParams) error { var txID transactions.Txid - err := txID.UnmarshalText([]byte(txid)) + err := txID.FromString(txid) if err != nil { return badRequest(ctx, err, errNoValidTxnSpecified, v2.Log) } @@ -1432,6 +1435,11 @@ func (v2 *Handlers) GetLedgerStateDelta(ctx echo.Context, round uint64, params m if err != nil { return notFound(ctx, err, fmt.Sprintf(errFailedRetrievingStateDelta, err), v2.Log) } + if handle == protocol.JSONStrictHandle { + // Zero out the Txleases map since it cannot be represented in JSON, as it is a map with an + // object key. + sDelta.Txleases = nil + } data, err := encode(handle, sDelta) if err != nil { return internalError(ctx, err, errFailedToEncodeResponse, v2.Log) @@ -1501,8 +1509,8 @@ func (v2 *Handlers) PendingTransactionInformation(ctx echo.Context, txid string, } txID := transactions.Txid{} - if err := txID.UnmarshalText([]byte(txid)); err != nil { - return badRequest(ctx, err, errNoValidTxnSpecified, v2.Log) + if err0 := txID.FromString(txid); err0 != nil { + return badRequest(ctx, err0, errNoValidTxnSpecified, v2.Log) } txn, ok := v2.Node.GetPendingTransaction(txID) @@ -2022,6 +2030,11 @@ func (v2 *Handlers) GetLedgerStateDeltaForTransactionGroup(ctx echo.Context, id if err != nil { return notFound(ctx, err, fmt.Sprintf(errFailedRetrievingStateDelta, err), v2.Log) } + if handle == protocol.JSONStrictHandle { + // Zero out the Txleases map since it cannot be represented in JSON, as it is a map with an + // object key. + delta.Txleases = nil + } data, err := encode(handle, delta) if err != nil { return internalError(ctx, err, errFailedToEncodeResponse, v2.Log) @@ -2044,6 +2057,13 @@ func (v2 *Handlers) GetTransactionGroupLedgerStateDeltasForRound(ctx echo.Contex if err != nil { return notFound(ctx, err, fmt.Sprintf(errFailedRetrievingStateDelta, err), v2.Log) } + if handle == protocol.JSONStrictHandle { + // Zero out the Txleases map since it cannot be represented in JSON, as it is a map with an + // object key. + for i := range deltas { + deltas[i].Delta.Txleases = nil + } + } response := struct { Deltas []eval.TxnGroupDeltaWithIds }{ diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 06ae38c0d6..4a6d5b6603 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -38,9 +38,9 @@ func (txid Txid) String() string { return fmt.Sprintf("%v", crypto.Digest(txid)) } -// UnmarshalText initializes the Address from an array of bytes. -func (txid *Txid) UnmarshalText(text []byte) error { - d, err := crypto.DigestFromString(string(text)) +// FromString initializes the Txid from a string +func (txid *Txid) FromString(text string) error { + d, err := crypto.DigestFromString(text) *txid = Txid(d) return err } diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index e72b0588d6..f3f1c67192 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -27,6 +27,7 @@ import ( algodclient "github.com/algorand/go-algorand/daemon/algod/api/client" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" kmdclient "github.com/algorand/go-algorand/daemon/kmd/client" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/config" @@ -819,7 +820,7 @@ func (c *Client) ParsedPendingTransaction(txid string) (txn v2.PreEncodedTxInfo, } // Block takes a round and returns its block -func (c *Client) Block(round uint64) (resp model.BlockResponse, err error) { +func (c *Client) Block(round uint64) (resp v2.BlockResponseJSON, err error) { algod, err := c.ensureAlgodClient() if err == nil { resp, err = algod.Block(round) @@ -1341,7 +1342,7 @@ func (c *Client) GetSyncRound() (rep model.GetSyncRoundResponse, err error) { } // GetLedgerStateDelta gets the LedgerStateDelta on a node w/ EnableFollowMode -func (c *Client) GetLedgerStateDelta(round uint64) (rep model.LedgerStateDeltaResponse, err error) { +func (c *Client) GetLedgerStateDelta(round uint64) (rep ledgercore.StateDelta, err error) { algod, err := c.ensureAlgodClient() if err == nil { return algod.GetLedgerStateDelta(round) diff --git a/test/e2e-go/features/devmode/devmode_test.go b/test/e2e-go/features/devmode/devmode_test.go index 525a6cacbd..bfb6a889ab 100644 --- a/test/e2e-go/features/devmode/devmode_test.go +++ b/test/e2e-go/features/devmode/devmode_test.go @@ -52,7 +52,7 @@ func testDevMode(t *testing.T, version protocol.ConsensusVersion) { firstRound := *txn.ConfirmedRound + 1 blk, err := fixture.AlgodClient.Block(*txn.ConfirmedRound) require.NoError(t, err) - seconds := int64(blk.Block["ts"].(float64)) + seconds := blk.Block.TimeStamp prevTime := time.Unix(seconds, 0) // Set Block timestamp offset to test that consecutive txns properly get their block time set const blkOffset = uint64(1_000_000) @@ -70,7 +70,7 @@ func testDevMode(t *testing.T, version protocol.ConsensusVersion) { require.Equal(t, round-1, uint64(txn.Txn.Txn.FirstValid)) newBlk, err := fixture.AlgodClient.Block(round) require.NoError(t, err) - newBlkSeconds := int64(newBlk.Block["ts"].(float64)) + newBlkSeconds := newBlk.Block.TimeStamp currTime := time.Unix(newBlkSeconds, 0) require.Equal(t, currTime, prevTime.Add(1_000_000*time.Second)) prevTime = currTime @@ -93,7 +93,18 @@ func testTxnGroupDeltasDevMode(t *testing.T, version protocol.ConsensusVersion) require.NoError(t, err) key := crypto.GenerateSignatureSecrets(crypto.Seed{}) receiver := basics.Address(key.SignatureVerifier) - txn := fixture.SendMoneyAndWait(0, 100000, 1000, sender.Address, receiver.String(), "") + + status, err := fixture.AlgodClient.Status() + require.NoError(t, err) + curRound := status.LastRound + + wh, err := fixture.LibGoalClient.GetUnencryptedWalletHandle() + require.NoError(t, err) + + fundingTx, err := fixture.LibGoalClient.SendPaymentFromWalletWithLease(wh, nil, sender.Address, receiver.String(), 1000, 100000, nil, "", [32]byte{1, 2, 3}, basics.Round(curRound).SubSaturate(1), 0) + require.NoError(t, err) + txn, err := fixture.WaitForConfirmedTxn(curRound+uint64(5), fundingTx.ID().String()) + require.NoError(t, err) require.NotNil(t, txn.ConfirmedRound) _, err = fixture.AlgodClient.Block(*txn.ConfirmedRound) require.NoError(t, err) @@ -101,16 +112,20 @@ func testTxnGroupDeltasDevMode(t *testing.T, version protocol.ConsensusVersion) // Test GetLedgerStateDeltaForTransactionGroup and verify the response contains a delta txngroupResponse, err := fixture.AlgodClient.GetLedgerStateDeltaForTransactionGroup(txn.Txn.ID().String()) require.NoError(t, err) - require.True(t, len(txngroupResponse) > 0) + require.NotZero(t, txngroupResponse) // Test GetTransactionGroupLedgerStateDeltasForRound and verify the response contains the delta for our txn roundResponse, err := fixture.AlgodClient.GetTransactionGroupLedgerStateDeltasForRound(1) require.NoError(t, err) require.Equal(t, len(roundResponse.Deltas), 1) groupDelta := roundResponse.Deltas[0] - require.Equal(t, 1, len(groupDelta.Ids)) + require.Len(t, groupDelta.Ids, 1) require.Equal(t, groupDelta.Ids[0], txn.Txn.ID().String()) // Assert that the TxIDs field across both endpoint responses is the same - require.Equal(t, txngroupResponse["Txids"], groupDelta.Delta["Txids"]) + require.Equal(t, txngroupResponse.Txids, groupDelta.Delta.Txids) + + // Txleases should always be nil for JSON responses + require.Nil(t, txngroupResponse.Txleases) + require.Nil(t, groupDelta.Delta.Txleases) } diff --git a/test/e2e-go/features/followernode/syncDeltas_test.go b/test/e2e-go/features/followernode/syncDeltas_test.go index b404a2a5ef..af27c7dda7 100644 --- a/test/e2e-go/features/followernode/syncDeltas_test.go +++ b/test/e2e-go/features/followernode/syncDeltas_test.go @@ -22,6 +22,9 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -52,6 +55,23 @@ func TestBasicSyncMode(t *testing.T) { nc, err := fixture.GetNodeController("Primary") a.NoError(err) + sender, err := fixture.GetRichestAccount() + require.NoError(t, err) + + status, err := fixture.AlgodClient.Status() + require.NoError(t, err) + curRound := status.LastRound + + wh, err := fixture.LibGoalClient.GetUnencryptedWalletHandle() + require.NoError(t, err) + + fundingTx, err := fixture.LibGoalClient.SendPaymentFromWalletWithLease(wh, nil, sender.Address, sender.Address, 0, 0, nil, "", [32]byte{1, 2, 3}, basics.Round(curRound).SubSaturate(1), 0) + require.NoError(t, err) + txn, err := fixture.WaitForConfirmedTxn(5, fundingTx.ID().String()) + require.NoError(t, err) + + require.LessOrEqual(t, *txn.ConfirmedRound, uint64(5), "Transaction should be confirmed in the first 5 rounds") + // Let the network make some progress waitForRound := uint64(5) err = fixture.ClientWaitForRoundWithTimeout(fixture.GetAlgodClientForController(nc), waitForRound) @@ -73,7 +93,22 @@ func TestBasicSyncMode(t *testing.T) { // retrieve state delta gResp, err := followClient.GetLedgerStateDelta(round) a.NoError(err) - a.NotNil(gResp) + a.NotZero(gResp) + + if round == *txn.ConfirmedRound { + // Txleases should always be nil for JSON responses + require.Nil(t, gResp.Txleases) + + // Verify that the transaction is in the state delta + expectedTxids := map[transactions.Txid]ledgercore.IncludedTransactions{ + txn.Txn.ID(): { + LastValid: txn.Txn.Txn.LastValid, + Intra: 0, + }, + } + require.Equal(t, expectedTxids, gResp.Txids) + } + // set sync round next err = followClient.SetSyncRound(round + 1) a.NoError(err) From f6c59a4c8166e69993953624a95b6d94a87bb003 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:20:08 -0400 Subject: [PATCH 208/218] p2p: Add algod_network_p2p_* traffic metrics (#6105) --- cmd/algod/main.go | 2 + network/metrics.go | 206 +++++++++++++++++++++++++++ network/metrics_test.go | 76 ++++++++++ network/p2p/p2p.go | 4 +- network/p2p/pubsub.go | 12 +- network/p2p/pubsubTracer.go | 98 ------------- network/p2pNetwork.go | 10 +- network/wsNetwork.go | 30 ---- network/wsPeer.go | 84 +++++------ test/heapwatch/block_history_plot.py | 16 ++- test/heapwatch/metrics_aggs.py | 27 +++- test/heapwatch/metrics_delta.py | 101 +++++++++---- test/heapwatch/metrics_lib.py | 2 +- test/heapwatch/requirements.txt | 4 +- util/metrics/metrics.go | 8 ++ 15 files changed, 466 insertions(+), 214 deletions(-) create mode 100644 network/metrics.go create mode 100644 network/metrics_test.go delete mode 100644 network/p2p/pubsubTracer.go diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 311b1507e6..47008256ac 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -445,6 +445,8 @@ var startupConfigCheckFields = []string{ "TxPoolExponentialIncreaseFactor", "TxPoolSize", "VerifiedTranscationsCacheSize", + "EnableP2P", + "EnableP2PHybridMode", } func resolveDataDir() string { diff --git a/network/metrics.go b/network/metrics.go new file mode 100644 index 0000000000..a1e92b2424 --- /dev/null +++ b/network/metrics.go @@ -0,0 +1,206 @@ +// Copyright (C) 2019-2024 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 network + +import ( + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/peer" + p2proto "github.com/libp2p/go-libp2p/core/protocol" + + "github.com/algorand/go-algorand/network/p2p" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/util/metrics" +) + +func init() { + // all tags are tracked by ws net + tagStringList := make([]string, 0, len(protocol.TagList)) + for _, t := range protocol.TagList { + tagStringList = append(tagStringList, string(t)) + } + networkSentBytesByTag = metrics.NewTagCounterFiltered("algod_network_sent_bytes_{TAG}", "Number of bytes that were sent over the network for {TAG} messages", tagStringList, "UNK") + networkReceivedBytesByTag = metrics.NewTagCounterFiltered("algod_network_received_bytes_{TAG}", "Number of bytes that were received from the network for {TAG} messages", tagStringList, "UNK") + networkMessageReceivedByTag = metrics.NewTagCounterFiltered("algod_network_message_received_{TAG}", "Number of complete messages that were received from the network for {TAG} messages", tagStringList, "UNK") + networkMessageSentByTag = metrics.NewTagCounterFiltered("algod_network_message_sent_{TAG}", "Number of complete messages that were sent to the network for {TAG} messages", tagStringList, "UNK") + networkHandleCountByTag = metrics.NewTagCounterFiltered("algod_network_rx_handle_countbytag_{TAG}", "count of handler calls in the receive thread for {TAG} messages", tagStringList, "UNK") + networkHandleMicrosByTag = metrics.NewTagCounterFiltered("algod_network_rx_handle_microsbytag_{TAG}", "microseconds spent by protocol handlers in the receive thread for {TAG} messages", tagStringList, "UNK") + + networkP2PSentBytesByTag = metrics.NewTagCounterFiltered("algod_network_p2p_sent_bytes_{TAG}", "Number of bytes that were sent over the network for {TAG} messages", tagStringList, "UNK") + networkP2PReceivedBytesByTag = metrics.NewTagCounterFiltered("algod_network_p2p_received_bytes_{TAG}", "Number of bytes that were received from the network for {TAG} messages", tagStringList, "UNK") + networkP2PMessageReceivedByTag = metrics.NewTagCounterFiltered("algod_network_p2p_message_received_{TAG}", "Number of complete messages that were received from the network for {TAG} messages", tagStringList, "UNK") + networkP2PMessageSentByTag = metrics.NewTagCounterFiltered("algod_network_p2p_message_sent_{TAG}", "Number of complete messages that were sent to the network for {TAG} messages", tagStringList, "UNK") +} + +var networkSentBytesTotal = metrics.MakeCounter(metrics.NetworkSentBytesTotal) +var networkP2PSentBytesTotal = metrics.MakeCounter(metrics.NetworkP2PSentBytesTotal) +var networkSentBytesByTag *metrics.TagCounter +var networkP2PSentBytesByTag *metrics.TagCounter +var networkReceivedBytesTotal = metrics.MakeCounter(metrics.NetworkReceivedBytesTotal) +var networkP2PReceivedBytesTotal = metrics.MakeCounter(metrics.NetworkP2PReceivedBytesTotal) +var networkReceivedBytesByTag *metrics.TagCounter +var networkP2PReceivedBytesByTag *metrics.TagCounter + +var networkMessageReceivedTotal = metrics.MakeCounter(metrics.NetworkMessageReceivedTotal) +var networkP2PMessageReceivedTotal = metrics.MakeCounter(metrics.NetworkP2PMessageReceivedTotal) +var networkMessageReceivedByTag *metrics.TagCounter +var networkP2PMessageReceivedByTag *metrics.TagCounter +var networkMessageSentTotal = metrics.MakeCounter(metrics.NetworkMessageSentTotal) +var networkP2PMessageSentTotal = metrics.MakeCounter(metrics.NetworkP2PMessageSentTotal) +var networkMessageSentByTag *metrics.TagCounter +var networkP2PMessageSentByTag *metrics.TagCounter + +var networkHandleMicrosByTag *metrics.TagCounter +var networkHandleCountByTag *metrics.TagCounter + +var networkConnectionsDroppedTotal = metrics.MakeCounter(metrics.NetworkConnectionsDroppedTotal) +var networkMessageQueueMicrosTotal = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_message_sent_queue_micros_total", Description: "Total microseconds message spent waiting in queue to be sent"}) +var networkP2PMessageQueueMicrosTotal = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_p2p_message_sent_queue_micros_total", Description: "Total microseconds p2p message spent waiting in queue to be sent"}) + +var duplicateNetworkMessageReceivedTotal = metrics.MakeCounter(metrics.DuplicateNetworkMessageReceivedTotal) +var duplicateNetworkMessageReceivedBytesTotal = metrics.MakeCounter(metrics.DuplicateNetworkMessageReceivedBytesTotal) +var duplicateNetworkFilterReceivedTotal = metrics.MakeCounter(metrics.DuplicateNetworkFilterReceivedTotal) +var outgoingNetworkMessageFilteredOutTotal = metrics.MakeCounter(metrics.OutgoingNetworkMessageFilteredOutTotal) +var outgoingNetworkMessageFilteredOutBytesTotal = metrics.MakeCounter(metrics.OutgoingNetworkMessageFilteredOutBytesTotal) +var unknownProtocolTagMessagesTotal = metrics.MakeCounter(metrics.UnknownProtocolTagMessagesTotal) + +var networkIncomingConnections = metrics.MakeGauge(metrics.NetworkIncomingConnections) +var networkOutgoingConnections = metrics.MakeGauge(metrics.NetworkOutgoingConnections) + +var networkIncomingBufferMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_rx_buffer_micros_total", Description: "microseconds spent by incoming messages on the receive buffer"}) +var networkHandleMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_rx_handle_micros_total", Description: "microseconds spent by protocol handlers in the receive thread"}) + +var networkBroadcasts = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcasts_total", Description: "number of broadcast operations"}) +var networkBroadcastQueueFull = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_queue_full_total", Description: "number of messages that were drops due to full broadcast queue"}) +var networkBroadcastQueueMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_queue_micros_total", Description: "microseconds broadcast requests sit on queue"}) +var networkBroadcastSendMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_send_micros_total", Description: "microseconds spent broadcasting"}) +var networkBroadcastsDropped = metrics.MakeCounter(metrics.MetricName{Name: "algod_broadcasts_dropped_total", Description: "number of broadcast messages not sent to any peer"}) +var networkPeerBroadcastDropped = metrics.MakeCounter(metrics.MetricName{Name: "algod_peer_broadcast_dropped_total", Description: "number of broadcast messages not sent to some peer"}) + +var networkPeerIdentityDisconnect = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_identity_duplicate", Description: "number of times identity challenge cause us to disconnect a peer"}) +var networkPeerIdentityError = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_identity_error", Description: "number of times an error occurs (besides expected) when processing identity challenges"}) +var networkPeerAlreadyClosed = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_peer_already_closed", Description: "number of times a peer would be added but the peer connection is already closed"}) + +var networkSlowPeerDrops = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_slow_drops_total", Description: "number of peers dropped for being slow to send to"}) +var networkIdlePeerDrops = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_idle_drops_total", Description: "number of peers dropped due to idle connection"}) + +var peers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peers", Description: "Number of active peers."}) +var incomingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_incoming_peers", Description: "Number of active incoming peers."}) +var outgoingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_outgoing_peers", Description: "Number of active outgoing peers."}) + +var transactionMessagesP2PRejectMessage = metrics.NewTagCounter(metrics.TransactionMessagesP2PRejectMessage.Name, metrics.TransactionMessagesP2PRejectMessage.Description) +var transactionMessagesP2PDuplicateMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PDuplicateMessage) +var transactionMessagesP2PDeliverMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PDeliverMessage) +var transactionMessagesP2PUnderdeliverableMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PUndeliverableMessage) + +var networkP2PGossipSubSentBytesTotal = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_p2p_gs_sent_bytes_total", Description: "Total number of bytes sent through gossipsub"}) +var networkP2PGossipSubReceivedBytesTotal = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_p2p_gs_received_bytes_total", Description: "Total number of bytes received through gossipsub"}) + +var _ = pubsub.RawTracer(pubsubMetricsTracer{}) + +// pubsubMetricsTracer is a tracer for pubsub events used to track metrics. +type pubsubMetricsTracer struct{} + +// AddPeer is invoked when a new peer is added. +func (t pubsubMetricsTracer) AddPeer(p peer.ID, proto p2proto.ID) {} + +// RemovePeer is invoked when a peer is removed. +func (t pubsubMetricsTracer) RemovePeer(p peer.ID) {} + +// Join is invoked when a new topic is joined +func (t pubsubMetricsTracer) Join(topic string) {} + +// Leave is invoked when a topic is abandoned +func (t pubsubMetricsTracer) Leave(topic string) {} + +// Graft is invoked when a new peer is grafted on the mesh (gossipsub) +func (t pubsubMetricsTracer) Graft(p peer.ID, topic string) {} + +// Prune is invoked when a peer is pruned from the message (gossipsub) +func (t pubsubMetricsTracer) Prune(p peer.ID, topic string) {} + +// ValidateMessage is invoked when a message first enters the validation pipeline. +func (t pubsubMetricsTracer) ValidateMessage(msg *pubsub.Message) { + if msg != nil && msg.Topic != nil { + switch *msg.Topic { + case p2p.TXTopicName: + networkP2PReceivedBytesTotal.AddUint64(uint64(len(msg.Data)), nil) + networkP2PReceivedBytesByTag.Add(string(protocol.TxnTag), uint64(len(msg.Data))) + networkP2PMessageReceivedByTag.Add(string(protocol.TxnTag), 1) + } + } +} + +// DeliverMessage is invoked when a message is delivered +func (t pubsubMetricsTracer) DeliverMessage(msg *pubsub.Message) { + transactionMessagesP2PDeliverMessage.Inc(nil) +} + +// RejectMessage is invoked when a message is Rejected or Ignored. +// The reason argument can be one of the named strings Reject*. +func (t pubsubMetricsTracer) RejectMessage(msg *pubsub.Message, reason string) { + // TagCounter cannot handle tags with spaces so pubsub.Reject* cannot be used directly. + // Since Go's strings are immutable, char replacement is a new allocation so that stick to string literals. + switch reason { + case pubsub.RejectValidationThrottled: + transactionMessagesP2PRejectMessage.Add("throttled", 1) + case pubsub.RejectValidationQueueFull: + transactionMessagesP2PRejectMessage.Add("full", 1) + case pubsub.RejectValidationFailed: + transactionMessagesP2PRejectMessage.Add("failed", 1) + case pubsub.RejectValidationIgnored: + transactionMessagesP2PRejectMessage.Add("ignored", 1) + default: + transactionMessagesP2PRejectMessage.Add("other", 1) + } +} + +// DuplicateMessage is invoked when a duplicate message is dropped. +func (t pubsubMetricsTracer) DuplicateMessage(msg *pubsub.Message) { + transactionMessagesP2PDuplicateMessage.Inc(nil) +} + +// ThrottlePeer is invoked when a peer is throttled by the peer gater. +func (t pubsubMetricsTracer) ThrottlePeer(p peer.ID) {} + +// RecvRPC is invoked when an incoming RPC is received. +func (t pubsubMetricsTracer) RecvRPC(rpc *pubsub.RPC) { + networkP2PGossipSubReceivedBytesTotal.AddUint64(uint64(rpc.Size()), nil) +} + +// SendRPC is invoked when a RPC is sent. +func (t pubsubMetricsTracer) SendRPC(rpc *pubsub.RPC, p peer.ID) { + networkP2PGossipSubSentBytesTotal.AddUint64(uint64(rpc.Size()), nil) + for i := range rpc.GetPublish() { + if rpc.Publish[i] != nil && rpc.Publish[i].Topic != nil { + switch *rpc.Publish[i].Topic { + case p2p.TXTopicName: + networkP2PSentBytesByTag.Add(string(protocol.TxnTag), uint64(len(rpc.Publish[i].Data))) + networkP2PSentBytesTotal.AddUint64(uint64(len(rpc.Publish[i].Data)), nil) + networkP2PMessageSentByTag.Add(string(protocol.TxnTag), 1) + } + } + } +} + +// DropRPC is invoked when an outbound RPC is dropped, typically because of a queue full. +func (t pubsubMetricsTracer) DropRPC(rpc *pubsub.RPC, p peer.ID) {} + +// UndeliverableMessage is invoked when the consumer of Subscribe is not reading messages fast enough and +// the pressure release mechanism trigger, dropping messages. +func (t pubsubMetricsTracer) UndeliverableMessage(msg *pubsub.Message) { + transactionMessagesP2PUnderdeliverableMessage.Inc(nil) +} diff --git a/network/metrics_test.go b/network/metrics_test.go new file mode 100644 index 0000000000..857ab57051 --- /dev/null +++ b/network/metrics_test.go @@ -0,0 +1,76 @@ +// Copyright (C) 2019-2024 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 network + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" +) + +// TestPubsubTracer_TagList makes sure pubsubMetricsTracer traces pubsub messages +// by counting switch cases in SendRPC and ValidateMessage +func TestMetrics_PubsubTracer_TagList(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "metrics.go", nil, 0) + require.NoError(t, err) + + // Find the SendRPC/ValidateMessage functions and count the switch cases + var sendCaseCount int + var recvCaseCount int + ast.Inspect(f, func(n ast.Node) bool { + switch stmt := n.(type) { + case *ast.FuncDecl: + if stmt.Name.Name == "SendRPC" { + ast.Inspect(stmt.Body, func(n ast.Node) bool { + if switchStmt, ok := n.(*ast.SwitchStmt); ok { + for _, stmt := range switchStmt.Body.List { + if _, ok := stmt.(*ast.CaseClause); ok { + sendCaseCount++ + } + } + } + return true + }) + } + if stmt.Name.Name == "ValidateMessage" { + ast.Inspect(stmt.Body, func(n ast.Node) bool { + if switchStmt, ok := n.(*ast.SwitchStmt); ok { + for _, stmt := range switchStmt.Body.List { + if _, ok := stmt.(*ast.CaseClause); ok { + recvCaseCount++ + } + } + } + return true + }) + } + } + return true + }) + + require.Equal(t, len(gossipSubTags), sendCaseCount) + require.Equal(t, len(gossipSubTags), recvCaseCount) +} diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 270dfb7476..4ddda54157 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -176,7 +176,7 @@ func configureResourceManager(cfg config.Local) (network.ResourceManager, error) } // MakeService creates a P2P service instance -func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler) (*serviceImpl, error) { +func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler, metricsTracer pubsub.RawTracer) (*serviceImpl, error) { sm := makeStreamManager(ctx, log, h, wsStreamHandler, cfg.EnableGossipService) h.Network().Notify(sm) @@ -188,7 +188,7 @@ func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h ho telemetryProtoInfo := formatPeerTelemetryInfoProtocolName(telemetryID, telemetryInstance) h.SetStreamHandler(protocol.ID(telemetryProtoInfo), func(s network.Stream) { s.Close() }) - ps, err := makePubSub(ctx, cfg, h) + ps, err := makePubSub(ctx, cfg, h, metricsTracer) if err != nil { return nil, err } diff --git a/network/p2p/pubsub.go b/network/p2p/pubsub.go index 657baecdde..a592657010 100644 --- a/network/p2p/pubsub.go +++ b/network/p2p/pubsub.go @@ -51,11 +51,14 @@ const ( ) // TXTopicName defines a pubsub topic for TX messages -const TXTopicName = "/algo/tx/0.1.0" +// There is a micro optimization for const string comparison: +// 8 bytes const string require a single x86-64 CMPQ instruction. +// Naming convention: "algo" + 2 bytes protocol tag + 2 bytes version +const TXTopicName = "algotx01" const incomingThreads = 20 // matches to number wsNetwork workers -func makePubSub(ctx context.Context, cfg config.Local, host host.Host) (*pubsub.PubSub, error) { +func makePubSub(ctx context.Context, cfg config.Local, host host.Host, metricsTracer pubsub.RawTracer) (*pubsub.PubSub, error) { //defaultParams := pubsub.DefaultGossipSubParams() options := []pubsub.Option{ @@ -98,7 +101,10 @@ func makePubSub(ctx context.Context, cfg config.Local, host host.Host) (*pubsub. pubsub.WithMessageSignaturePolicy(pubsub.StrictNoSign), // pubsub.WithValidateThrottle(cfg.TxBacklogSize), pubsub.WithValidateWorkers(incomingThreads), - pubsub.WithRawTracer(pubsubTracer{}), + } + + if metricsTracer != nil { + options = append(options, pubsub.WithRawTracer(metricsTracer)) } return pubsub.NewGossipSub(ctx, host, options...) diff --git a/network/p2p/pubsubTracer.go b/network/p2p/pubsubTracer.go deleted file mode 100644 index ca57bc69ce..0000000000 --- a/network/p2p/pubsubTracer.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (C) 2019-2024 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 ( - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/core/protocol" - - "github.com/algorand/go-algorand/util/metrics" -) - -var _ = pubsub.RawTracer(pubsubTracer{}) - -var transactionMessagesP2PRejectMessage = metrics.NewTagCounter(metrics.TransactionMessagesP2PRejectMessage.Name, metrics.TransactionMessagesP2PRejectMessage.Description) -var transactionMessagesP2PDuplicateMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PDuplicateMessage) -var transactionMessagesP2PDeliverMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PDeliverMessage) -var transactionMessagesP2PUnderdeliverableMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PUndeliverableMessage) -var transactionMessagesP2PValidateMessage = metrics.MakeCounter(metrics.TransactionMessagesP2PValidateMessage) - -// pubsubTracer is a tracer for pubsub events used to track metrics. -type pubsubTracer struct{} - -// AddPeer is invoked when a new peer is added. -func (t pubsubTracer) AddPeer(p peer.ID, proto protocol.ID) {} - -// RemovePeer is invoked when a peer is removed. -func (t pubsubTracer) RemovePeer(p peer.ID) {} - -// Join is invoked when a new topic is joined -func (t pubsubTracer) Join(topic string) {} - -// Leave is invoked when a topic is abandoned -func (t pubsubTracer) Leave(topic string) {} - -// Graft is invoked when a new peer is grafted on the mesh (gossipsub) -func (t pubsubTracer) Graft(p peer.ID, topic string) {} - -// Prune is invoked when a peer is pruned from the message (gossipsub) -func (t pubsubTracer) Prune(p peer.ID, topic string) {} - -// ValidateMessage is invoked when a message first enters the validation pipeline. -func (t pubsubTracer) ValidateMessage(msg *pubsub.Message) { - transactionMessagesP2PValidateMessage.Inc(nil) -} - -// DeliverMessage is invoked when a message is delivered -func (t pubsubTracer) DeliverMessage(msg *pubsub.Message) { - transactionMessagesP2PDeliverMessage.Inc(nil) -} - -// RejectMessage is invoked when a message is Rejected or Ignored. -// The reason argument can be one of the named strings Reject*. -func (t pubsubTracer) RejectMessage(msg *pubsub.Message, reason string) { - switch reason { - case pubsub.RejectValidationThrottled, pubsub.RejectValidationQueueFull, pubsub.RejectValidationFailed, pubsub.RejectValidationIgnored: - transactionMessagesP2PRejectMessage.Add(reason, 1) - default: - transactionMessagesP2PRejectMessage.Add("other", 1) - } -} - -// DuplicateMessage is invoked when a duplicate message is dropped. -func (t pubsubTracer) DuplicateMessage(msg *pubsub.Message) { - transactionMessagesP2PDuplicateMessage.Inc(nil) -} - -// ThrottlePeer is invoked when a peer is throttled by the peer gater. -func (t pubsubTracer) ThrottlePeer(p peer.ID) {} - -// RecvRPC is invoked when an incoming RPC is received. -func (t pubsubTracer) RecvRPC(rpc *pubsub.RPC) {} - -// SendRPC is invoked when a RPC is sent. -func (t pubsubTracer) SendRPC(rpc *pubsub.RPC, p peer.ID) {} - -// DropRPC is invoked when an outbound RPC is dropped, typically because of a queue full. -func (t pubsubTracer) DropRPC(rpc *pubsub.RPC, p peer.ID) {} - -// UndeliverableMessage is invoked when the consumer of Subscribe is not reading messages fast enough and -// the pressure release mechanism trigger, dropping messages. -func (t pubsubTracer) UndeliverableMessage(msg *pubsub.Message) { - transactionMessagesP2PUnderdeliverableMessage.Inc(nil) -} diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index f9dc04b785..d3af60a223 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -194,6 +194,11 @@ type p2pPeerStats struct { txReceived atomic.Uint64 } +// gossipSubTags defines protocol messages that are relayed using GossipSub +var gossipSubTags = map[protocol.Tag]string{ + protocol.TxnTag: p2p.TXTopicName, +} + // NewP2PNetwork returns an instance of GossipNode that uses the p2p.Service func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebookAddresses []string, genesisID string, networkID protocol.NetworkID, node NodeInfo, identityOpts *identityOpts) (*P2PNetwork, error) { const readBufferLen = 2048 @@ -214,7 +219,7 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo config: cfg, genesisID: genesisID, networkID: networkID, - topicTags: map[protocol.Tag]string{protocol.TxnTag: p2p.TXTopicName}, + topicTags: gossipSubTags, wsPeers: make(map[peer.ID]*wsPeer), wsPeersToIDs: make(map[*wsPeer]peer.ID), peerStats: make(map[peer.ID]*p2pPeerStats), @@ -261,7 +266,7 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo } log.Infof("P2P host created: peer ID %s addrs %s", h.ID(), h.Addrs()) - net.service, err = p2p.MakeService(net.ctx, log, cfg, h, la, net.wsStreamHandler) + net.service, err = p2p.MakeService(net.ctx, log, cfg, h, la, net.wsStreamHandler, pubsubMetricsTracer{}) if err != nil { return nil, err } @@ -791,6 +796,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea conn: &wsPeerConnP2P{stream: stream}, outgoing: !incoming, identity: netIdentPeerID, + peerType: peerTypeP2P, } protos, err := n.pstore.GetProtocols(p2pPeer) if err != nil { diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 2af3a9b6bf..ecb636c8e2 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -53,7 +53,6 @@ import ( tools_network "github.com/algorand/go-algorand/tools/network" "github.com/algorand/go-algorand/tools/network/dnssec" "github.com/algorand/go-algorand/util" - "github.com/algorand/go-algorand/util/metrics" ) const incomingThreads = 20 @@ -117,35 +116,6 @@ const wsMaxHeaderBytes = 4096 // used from the ReservedFDs pool, as this pool is meant for short-lived usage (dns queries, disk i/o, etc.) const ReservedHealthServiceConnections = 10 -var networkIncomingConnections = metrics.MakeGauge(metrics.NetworkIncomingConnections) -var networkOutgoingConnections = metrics.MakeGauge(metrics.NetworkOutgoingConnections) - -var networkIncomingBufferMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_rx_buffer_micros_total", Description: "microseconds spent by incoming messages on the receive buffer"}) -var networkHandleMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_rx_handle_micros_total", Description: "microseconds spent by protocol handlers in the receive thread"}) - -var networkBroadcasts = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcasts_total", Description: "number of broadcast operations"}) -var networkBroadcastQueueFull = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_queue_full_total", Description: "number of messages that were drops due to full broadcast queue"}) -var networkBroadcastQueueMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_queue_micros_total", Description: "microseconds broadcast requests sit on queue"}) -var networkBroadcastSendMicros = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_broadcast_send_micros_total", Description: "microseconds spent broadcasting"}) -var networkBroadcastsDropped = metrics.MakeCounter(metrics.MetricName{Name: "algod_broadcasts_dropped_total", Description: "number of broadcast messages not sent to any peer"}) -var networkPeerBroadcastDropped = metrics.MakeCounter(metrics.MetricName{Name: "algod_peer_broadcast_dropped_total", Description: "number of broadcast messages not sent to some peer"}) - -var networkPeerIdentityDisconnect = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_identity_duplicate", Description: "number of times identity challenge cause us to disconnect a peer"}) -var networkPeerIdentityError = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_identity_error", Description: "number of times an error occurs (besides expected) when processing identity challenges"}) -var networkPeerAlreadyClosed = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_peer_already_closed", Description: "number of times a peer would be added but the peer connection is already closed"}) - -var networkSlowPeerDrops = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_slow_drops_total", Description: "number of peers dropped for being slow to send to"}) -var networkIdlePeerDrops = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_idle_drops_total", Description: "number of peers dropped due to idle connection"}) - -var minPing = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peer_min_ping_seconds", Description: "Network round trip time to fastest peer in seconds."}) -var meanPing = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peer_mean_ping_seconds", Description: "Network round trip time to average peer in seconds."}) -var medianPing = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peer_median_ping_seconds", Description: "Network round trip time to median peer in seconds."}) -var maxPing = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peer_max_ping_seconds", Description: "Network round trip time to slowest peer in seconds."}) - -var peers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_peers", Description: "Number of active peers."}) -var incomingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_incoming_peers", Description: "Number of active incoming peers."}) -var outgoingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_outgoing_peers", Description: "Number of active outgoing peers."}) - // peerDisconnectionAckDuration defines the time we would wait for the peer disconnection to complete. const peerDisconnectionAckDuration = 5 * time.Second diff --git a/network/wsPeer.go b/network/wsPeer.go index 88a0c615f9..a6a982af59 100644 --- a/network/wsPeer.go +++ b/network/wsPeer.go @@ -39,7 +39,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util" - "github.com/algorand/go-algorand/util/metrics" ) // MaxMessageLength is the maximum length of a message that can be sent or received, exported to be used in the node.TestMaxSizesCorrect test @@ -52,20 +51,7 @@ const averageMessageLength = 2 * 1024 // Most of the messages are smaller tha // buffer and starve messages from other peers. const msgsInReadBufferPerPeer = 10 -var tagStringList []string - func init() { - tagStringList = make([]string, len(protocol.TagList)) - for i, t := range protocol.TagList { - tagStringList[i] = string(t) - } - networkSentBytesByTag = metrics.NewTagCounterFiltered("algod_network_sent_bytes_{TAG}", "Number of bytes that were sent over the network for {TAG} messages", tagStringList, "UNK") - networkReceivedBytesByTag = metrics.NewTagCounterFiltered("algod_network_received_bytes_{TAG}", "Number of bytes that were received from the network for {TAG} messages", tagStringList, "UNK") - networkMessageReceivedByTag = metrics.NewTagCounterFiltered("algod_network_message_received_{TAG}", "Number of complete messages that were received from the network for {TAG} messages", tagStringList, "UNK") - networkMessageSentByTag = metrics.NewTagCounterFiltered("algod_network_message_sent_{TAG}", "Number of complete messages that were sent to the network for {TAG} messages", tagStringList, "UNK") - networkHandleCountByTag = metrics.NewTagCounterFiltered("algod_network_rx_handle_countbytag_{TAG}", "count of handler calls in the receive thread for {TAG} messages", tagStringList, "UNK") - networkHandleMicrosByTag = metrics.NewTagCounterFiltered("algod_network_rx_handle_microsbytag_{TAG}", "microseconds spent by protocol handlers in the receive thread for {TAG} messages", tagStringList, "UNK") - matched := false for _, version := range SupportedProtocolVersions { if version == versionPeerFeatures { @@ -83,29 +69,6 @@ func init() { } } -var networkSentBytesTotal = metrics.MakeCounter(metrics.NetworkSentBytesTotal) -var networkSentBytesByTag *metrics.TagCounter -var networkReceivedBytesTotal = metrics.MakeCounter(metrics.NetworkReceivedBytesTotal) -var networkReceivedBytesByTag *metrics.TagCounter - -var networkMessageReceivedTotal = metrics.MakeCounter(metrics.NetworkMessageReceivedTotal) -var networkMessageReceivedByTag *metrics.TagCounter -var networkMessageSentTotal = metrics.MakeCounter(metrics.NetworkMessageSentTotal) -var networkMessageSentByTag *metrics.TagCounter - -var networkHandleMicrosByTag *metrics.TagCounter -var networkHandleCountByTag *metrics.TagCounter - -var networkConnectionsDroppedTotal = metrics.MakeCounter(metrics.NetworkConnectionsDroppedTotal) -var networkMessageQueueMicrosTotal = metrics.MakeCounter(metrics.MetricName{Name: "algod_network_message_sent_queue_micros_total", Description: "Total microseconds message spent waiting in queue to be sent"}) - -var duplicateNetworkMessageReceivedTotal = metrics.MakeCounter(metrics.DuplicateNetworkMessageReceivedTotal) -var duplicateNetworkMessageReceivedBytesTotal = metrics.MakeCounter(metrics.DuplicateNetworkMessageReceivedBytesTotal) -var duplicateNetworkFilterReceivedTotal = metrics.MakeCounter(metrics.DuplicateNetworkFilterReceivedTotal) -var outgoingNetworkMessageFilteredOutTotal = metrics.MakeCounter(metrics.OutgoingNetworkMessageFilteredOutTotal) -var outgoingNetworkMessageFilteredOutBytesTotal = metrics.MakeCounter(metrics.OutgoingNetworkMessageFilteredOutBytesTotal) -var unknownProtocolTagMessagesTotal = metrics.MakeCounter(metrics.UnknownProtocolTagMessagesTotal) - // defaultSendMessageTags is the default list of messages which a peer would // allow to be sent without receiving any explicit request. var defaultSendMessageTags = map[protocol.Tag]bool{ @@ -204,6 +167,16 @@ type sendMessages struct { onRelease func() } +//msgp:ignore peerType +type peerType int + +const ( + // peerTypeWs is a peer that is connected over a websocket connection + peerTypeWs peerType = iota + // peerTypeP2P is a peer that is connected over an P2P connection + peerTypeP2P +) + 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 @@ -318,6 +291,10 @@ type wsPeer struct { // closers is a slice of functions to run when the peer is closed closers []func() + + // peerType defines the peer's underlying connection type + // used for separate p2p vs ws metrics + peerType peerType } // HTTPPeer is what the opaque Peer might be. @@ -639,10 +616,17 @@ func (wp *wsPeer) readLoop() { } msg.Net = wp.net 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)) - networkMessageReceivedByTag.Add(string(tag[:]), 1) + if wp.peerType == peerTypeWs { + networkReceivedBytesTotal.AddUint64(uint64(len(msg.Data)+2), nil) + networkMessageReceivedTotal.AddUint64(1, nil) + networkReceivedBytesByTag.Add(string(tag[:]), uint64(len(msg.Data)+2)) + networkMessageReceivedByTag.Add(string(tag[:]), 1) + } else { + networkP2PReceivedBytesTotal.AddUint64(uint64(len(msg.Data)+2), nil) + networkP2PMessageReceivedTotal.AddUint64(1, nil) + networkP2PReceivedBytesByTag.Add(string(tag[:]), uint64(len(msg.Data)+2)) + networkP2PMessageReceivedByTag.Add(string(tag[:]), 1) + } msg.Sender = wp // for outgoing connections, we want to notify the connection monitor that we've received @@ -863,11 +847,19 @@ func (wp *wsPeer) writeLoopSendMsg(msg sendMessage) disconnectReason { return disconnectWriteError } 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) - networkMessageSentByTag.Add(string(tag), 1) - networkMessageQueueMicrosTotal.AddUint64(uint64(time.Now().Sub(msg.peerEnqueued).Nanoseconds()/1000), nil) + if wp.peerType == peerTypeWs { + networkSentBytesTotal.AddUint64(uint64(len(msg.data)), nil) + networkSentBytesByTag.Add(string(tag), uint64(len(msg.data))) + networkMessageSentTotal.AddUint64(1, nil) + networkMessageSentByTag.Add(string(tag), 1) + networkMessageQueueMicrosTotal.AddUint64(uint64(time.Since(msg.peerEnqueued).Nanoseconds()/1000), nil) + } else { + networkP2PSentBytesTotal.AddUint64(uint64(len(msg.data)), nil) + networkP2PSentBytesByTag.Add(string(tag), uint64(len(msg.data))) + networkP2PMessageSentTotal.AddUint64(1, nil) + networkP2PMessageSentByTag.Add(string(tag), 1) + networkP2PMessageQueueMicrosTotal.AddUint64(uint64(time.Since(msg.peerEnqueued).Nanoseconds()/1000), nil) + } return disconnectReasonNone } diff --git a/test/heapwatch/block_history_plot.py b/test/heapwatch/block_history_plot.py index d8c86b454f..0bf8a2c8a2 100644 --- a/test/heapwatch/block_history_plot.py +++ b/test/heapwatch/block_history_plot.py @@ -23,6 +23,7 @@ # Graph over time of TPS or 10-round-moving-average-TPS import base64 +import json import os import statistics import sys @@ -106,12 +107,25 @@ def process(path, args): prevtc = tc prevts = ts prevtime = _time - print('{} blocks, block txns [{}-{}], block seconds [{}-{}], tps [{}-{}]'.format( + print('{} blocks, block txns [{}-{}], block seconds [{}-{}], tps [{}-{}], total txns {}'.format( count, mintxn,maxtxn, mindt,maxdt, mintps,maxtps, + tc, )) + if tc > 0: + with open(path + '.stats', 'w') as fout: + fout.write(json.dumps({ + 'blocks': count, + 'tc': tc, + 'mintxn': mintxn, + 'maxtxn': maxtxn, + 'mindt': mindt, + 'maxdt': maxdt, + 'mintps': mintps, + 'maxtps': maxtps, + })) start = 0 end = len(txnv)-1 diff --git a/test/heapwatch/metrics_aggs.py b/test/heapwatch/metrics_aggs.py index 0189634be5..d20593c097 100644 --- a/test/heapwatch/metrics_aggs.py +++ b/test/heapwatch/metrics_aggs.py @@ -33,7 +33,7 @@ from plotly.subplots import make_subplots -from metrics_lib import MetricType, parse_metrics, gather_metrics_files_by_nick +from metrics_lib import Metric, MetricType, parse_metrics, gather_metrics_files_by_nick logger = logging.getLogger(__name__) @@ -53,6 +53,8 @@ def main(): ap.add_argument('--nick-lre', action='append', default=[], help='label:regexp to filter node names, may be repeated') ap.add_argument('-s', '--save', type=str, choices=['png', 'html'], help=f'save plot to \'{default_img_filename}\' or \'{default_html_filename}\' file instead of showing it') ap.add_argument('--verbose', default=False, action='store_true') + ap.add_argument('--avg-max', default=False, action='store_true', help='print avg of max values across nodes for each metric') + ap.add_argument('--avg-max-min', default=False, action='store_true', help='print avg of max-min values across nodes for each metric') args = ap.parse_args() if args.verbose: @@ -99,6 +101,7 @@ def main(): } fig['layout']['height'] = 500 * nrows + nick_series = {} for nick, files_by_date in filesByNick.items(): active_metrics = {} @@ -146,7 +149,10 @@ def main(): active_metric_names.sort() active_metrics[full_name] = active_metric_names idx += 1 - + + if args.avg_max or args.avg_max_min: + nick_series[nick] = raw_series + for i, metric_pair in enumerate(sorted(active_metrics.items())): metric_name, metric_fullnames = metric_pair for metric_fullname in metric_fullnames: @@ -158,6 +164,23 @@ def main(): line=dict(width=1), ), i+1, 1) + if args.avg_max or args.avg_max_min: + metric_names_nick_max_avg = {} + for nick, raw_series in nick_series.items(): + for metric_name, rw in raw_series.items(): + mmax = max(rw) + mmin = min(rw) + print(f'{nick}: {metric_name}: count {len(rw)}, max {mmax}, min {mmin}, min-max {mmax - mmin}') + metric = Metric(metric_name, 0, MetricType.COUNTER) + if metric.short_name() not in metric_names_nick_max_avg: + metric_names_nick_max_avg[metric.short_name()] = [] + if args.avg_max_min: + metric_names_nick_max_avg[metric.short_name()].append(mmax - mmin) + if args.avg_max: + metric_names_nick_max_avg[metric.short_name()].append(mmax) + for metric_name, val in metric_names_nick_max_avg.items(): + print(f'{metric_name}: avg {sum(val)/len(val)}') + if args.save: if args.save == 'html': target_path = os.path.join(args.dir, default_html_filename) diff --git a/test/heapwatch/metrics_delta.py b/test/heapwatch/metrics_delta.py index 2d64ee097a..3ff1afca65 100644 --- a/test/heapwatch/metrics_delta.py +++ b/test/heapwatch/metrics_delta.py @@ -136,6 +136,8 @@ def __init__(self, label=None): self.tpsMeanSum = 0 self.txBpsMeanSum = 0 self.rxBpsMeanSum = 0 + self.txP2PBpsMeanSum = 0 + self.rxP2PBpsMeanSum = 0 self.tpsSum = 0 self.blockTimeSum = 0 self.sumsCount = 0 @@ -152,6 +154,8 @@ def __call__(self, ttr, nick): self.tpsMeanSum += meanOrZero(ttr.tpsList) self.txBpsMeanSum += meanOrZero(ttr.txBpsList) self.rxBpsMeanSum += meanOrZero(ttr.rxBpsList) + self.txP2PBpsMeanSum += meanOrZero(ttr.txP2PBpsList) + self.rxP2PBpsMeanSum += meanOrZero(ttr.rxP2PBpsList) self.tpsSum += ttr.tps self.blockTimeSum += ttr.blockTime self.sumsCount += 1 @@ -164,8 +168,10 @@ def blockinfo(self, curtime): return self.biByTime.get(curtime) def byMsg(self, html=False): - txPSums = {} - rxPSums = {} + txWsPSums = {} + rxWsPSums = {} + txP2PPSums = {} + rxP2PPSums = {} secondsSum = 0 txMax = {} txMin = {} @@ -175,8 +181,10 @@ def byMsg(self, html=False): for nick, ns in self.nodes.items(): nicks.append(nick) secondsSum += ns.secondsSum - dictSum(txPSums, ns.txPSums) - dictSum(rxPSums, ns.rxPSums) + dictSum(txWsPSums, ns.txPSums) + dictSum(rxWsPSums, ns.rxPSums) + dictSum(txP2PPSums, ns.txP2PPSums) + dictSum(rxP2PPSums, ns.rxP2PPSums) dictMax(txMax, ns.txPLists) dictMax(rxMax, ns.rxPLists) dictMin(txMin, ns.txPLists) @@ -185,23 +193,36 @@ def byMsg(self, html=False): lines = [] if html: lines.append('
{}
'.format(nodesummary)) - lines.append('') + lines.append('
tx B/srx B/s
') # traffic per tag two columns: ws and p2p else: lines.append(nodesummary) - lines.append('\ttx B/s\trx B/s') - for msg, txB in txPSums.items(): - if msg not in rxPSums: - rxPSums[msg] = 0 - for rxBps, msg in sorted([(rxB/secondsSum, msg) for msg, rxB in rxPSums.items()], reverse=True): - txBps = txPSums.get(msg,0)/secondsSum - if (txBps < 0.5) and (rxBps < 0.5): - continue + + for title, txPSums, rxPSums in [ + ('ws', txWsPSums, rxWsPSums), + ('p2p', txP2PPSums, rxP2PPSums), + ]: if html: - lines.append(''.format(msg, txBps, rxBps)) + lines.append('') if html: - lines.append('
{}{:.0f}{:.0f}
') + lines.append(f'') else: - lines.append('{}\t{:.0f}\t{:.0f}'.format(msg, txBps, rxBps)) + lines.append(f'{title} traffic per tag') + lines.append('\ttx B/s\trx B/s') + for msg, txB in txPSums.items(): + if msg not in rxPSums: + rxPSums[msg] = 0 + for rxBps, msg in sorted([(rxB/secondsSum, msg) for msg, rxB in rxPSums.items()], reverse=True): + txBps = txPSums.get(msg,0)/secondsSum + if (txBps < 0.5) and (rxBps < 0.5): + continue + if html: + lines.append(''.format(msg, txBps, rxBps)) + else: + lines.append('{}\t{:.0f}\t{:.0f}'.format(msg, txBps, rxBps)) + if html: + lines.append('
{title} traffic per tag
tx B/srx B/s
{}{:.0f}{:.0f}
') + lines.append('
') + lines.append('') # traffic per tag two columns: ws and p2p return '\n'.join(lines) def txPool(self): @@ -230,7 +251,7 @@ def html(self): def str(self, html=False): if not self.sumsCount: - tps, txbps, rxbps = math.nan, math.nan, math.nan + tps, txbps, rxbps, txP2Pbps, rxP2Pbps = math.nan, math.nan, math.nan, math.nan, math.nan blockTimes = math.nan else: #tps = self.tpsMeanSum/self.sumsCount @@ -238,6 +259,8 @@ def str(self, html=False): blockTimes = self.blockTimeSum/self.sumsCount txbps = self.txBpsMeanSum/self.sumsCount rxbps = self.rxBpsMeanSum/self.sumsCount + txP2Pbps = self.txP2PBpsMeanSum/self.sumsCount + rxP2Pbps = self.rxP2PBpsMeanSum/self.sumsCount labelspace = "" if self.label: labelspace = self.label + " " @@ -248,12 +271,12 @@ def str(self, html=False): else: verifyMillis = '' if html: - fmt = '{byMsg}\n{verifyMillis}
{labelspace}{txPool}
\n
{labelspace}summary: {TPS:0.2f} TPS, {bt:1.2f}s/block, tx {txBps}B/s, rx {rxBps}B/s
' + fmt = '{byMsg}\n{verifyMillis}
{labelspace}{txPool}
\n
{labelspace}summary: {TPS:0.2f} TPS, {bt:1.2f}s/block, tx {txBps}B/s, rx {rxBps}B/s, p2p tx {txP2PBps}B/s, p2p rx {rxP2PBps}B/s
' if self.label: fmt = '
' + self.label + '
' + fmt else: - fmt = '{byMsg}\n{verifyMillis}{labelspace}{txPool}\n{labelspace}summary: {TPS:0.2f} TPS, {bt:1.2f}s/block, tx {txBps}B/s, rx {rxBps}B/s' - return fmt.format(labelspace=labelspace, byMsg=self.byMsg(html), txPool=self.txPool(), TPS=tps, txBps=hunum(txbps), rxBps=hunum(rxbps), bt=blockTimes, verifyMillis=verifyMillis) + fmt = '{byMsg}\n{verifyMillis}{labelspace}{txPool}\n{labelspace}summary: {TPS:0.2f} TPS, {bt:1.2f}s/block, tx {txBps}B/s, rx {rxBps}B/s, p2p tx {txP2PBps}B/s, p2p rx {rxP2PBps}B/s' + return fmt.format(labelspace=labelspace, byMsg=self.byMsg(html), txPool=self.txPool(), TPS=tps, txBps=hunum(txbps), rxBps=hunum(rxbps), txP2PBps=hunum(txP2Pbps), rxP2PBps=hunum(rxP2Pbps), bt=blockTimes, verifyMillis=verifyMillis) def plot_pool(self, outpath): from matplotlib import pyplot as plt @@ -486,17 +509,27 @@ def __init__(self): self.deltas = [] self.txBpsList = [] self.rxBpsList = [] + self.txP2PBpsList = [] + self.rxP2PBpsList = [] self.tpsList = [] self.txBSum = 0 self.rxBSum = 0 + self.txP2PBSum = 0 + self.rxP2PBSum = 0 self.txnSum = 0 self.secondsSum = 0 # algod_network_received_bytes_* self.rxPLists = {} self.rxPSums = {} + # algod_network_p2p_received_bytes_* + self.rxP2PPLists = {} + self.rxP2PPSums = {} # algod_network_sent_bytes_* self.txPLists = {} self.txPSums = {} + # algod_network_p2p_sent_bytes_* + self.txP2PPLists = {} + self.txP2PPSums = {} self.times = [] # algod_tx_pool_count self.txPool = [] @@ -533,7 +566,7 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): reportpath = args.report[:-4] + nick + '.csv' reportf = open(reportpath, 'wt') writer = csv.writer(reportf) - writer.writerow(('when', 'tx bytes/s', 'rx bytes/s','TPS', 's/block')) + writer.writerow(('when', 'tx bytes/s', 'rx bytes/s', 'tx p2p bytes/s', 'rx p2p bytes/s', 'TPS', 's/block')) prev = None prevtime = None prevPath = None @@ -587,6 +620,11 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): rxBytes = d.get('algod_network_received_bytes_total',0) txBytesPerSec = txBytes / dt rxBytesPerSec = rxBytes / dt + txP2PBytes = d.get('algod_network_p2p_sent_bytes_total',0) + rxP2PBytes = d.get('algod_network_p2p_received_bytes_total',0) + txP2PBytesPerSec = txP2PBytes / dt + rxP2PBytesPerSec = rxP2PBytes / dt + # TODO: gather algod_network_sent_bytes_* and algod_network_received_bytes_* if (tps is None) or ((args.mintps is not None) and (tps < args.mintps)): # do not sum up this row @@ -594,18 +632,26 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): else: self.txBpsList.append(txBytesPerSec) self.rxBpsList.append(rxBytesPerSec) + self.txP2PBpsList.append(txP2PBytesPerSec) + self.rxP2PBpsList.append(rxP2PBytesPerSec) self.tpsList.append(tps) self.txBSum += txBytes self.rxBSum += rxBytes + self.txP2PBSum += txP2PBytes + self.rxP2PBSum += rxP2PBytes self.txnSum += txnCount self.secondsSum += dt perProtocol('algod_network_sent_bytes_', self.txPLists, self.txPSums, d, dt) perProtocol('algod_network_received_bytes_', self.rxPLists, self.rxPSums, d, dt) + perProtocol('algod_network_p2p_sent_bytes_', self.txP2PPLists, self.txP2PPSums, d, dt) + perProtocol('algod_network_p2p_received_bytes_', self.rxP2PPLists, self.rxP2PPSums, d, dt) if writer: writer.writerow(( time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(curtime)), txBytesPerSec, rxBytesPerSec, + txP2PBytesPerSec, + rxP2PBytesPerSec, tps, blocktime, )) @@ -631,13 +677,14 @@ def process_files(self, args, nick=None, metrics_files=None, bisource=None): self.blockTime = totalDt / rounds if writer and self.txBpsList: writer.writerow([]) - for bsum, msg in sorted([(bsum,msg) for msg,bsum in self.txPSums.items()]): - pass + # TODO: summarize + # for bsum, msg in sorted([(bsum,msg) for msg,bsum in self.txPSums.items()]): + # pass writer.writerow([]) - writer.writerow(['min', min(self.txBpsList), min(self.rxBpsList), min(self.tpsList)]) - writer.writerow(['avg', self.txBSum/self.secondsSum, self.rxBSum/self.secondsSum, self.txnSum/self.secondsSum]) - writer.writerow(['max', max(self.txBpsList), max(self.rxBpsList), max(self.tpsList)]) - writer.writerow(['std', statistics.pstdev(self.txBpsList), statistics.pstdev(self.rxBpsList), statistics.pstdev(self.tpsList)]) + writer.writerow(['min', min(self.txBpsList), min(self.rxBpsList), min(self.txP2PBpsList), min(self.rxP2PBpsList), min(self.tpsList)]) + writer.writerow(['avg', self.txBSum/self.secondsSum, self.rxBSum/self.secondsSum, self.txP2PBSum/self.secondsSum, self.rxP2PBSum/self.secondsSum, self.txnSum/self.secondsSum]) + writer.writerow(['max', max(self.txBpsList), max(self.rxBpsList), max(self.txP2PBpsList), max(self.rxP2PBpsList), max(self.tpsList)]) + writer.writerow(['std', statistics.pstdev(self.txBpsList), statistics.pstdev(self.rxBpsList), statistics.pstdev(self.txP2PBpsList), statistics.pstdev(self.rxP2PBpsList), statistics.pstdev(self.tpsList)]) if reportf: reportf.close() if self.deltas and args.deltas: diff --git a/test/heapwatch/metrics_lib.py b/test/heapwatch/metrics_lib.py index fbda555b90..5fc7b36075 100644 --- a/test/heapwatch/metrics_lib.py +++ b/test/heapwatch/metrics_lib.py @@ -54,7 +54,7 @@ def hunum(x): return '{:.1f}k'.format(x / 1000.0) if x >= 1000: return '{:.2f}k'.format(x / 1000.0) - return '{:.2f}x'.format(x) + return '{:.2f}'.format(x) def test_metric_line_re(): diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt index db92372c6d..cf443a24e4 100644 --- a/test/heapwatch/requirements.txt +++ b/test/heapwatch/requirements.txt @@ -6,5 +6,5 @@ plotly==5.16.0 py-algorand-sdk==2.3.0 kaleido==0.2.1 networkx==3.3 -gravis=0.1.0 -termcolor=2.4.0 +gravis==0.1.0 +termcolor==2.4.0 diff --git a/util/metrics/metrics.go b/util/metrics/metrics.go index fcc566312f..d7afe6439c 100644 --- a/util/metrics/metrics.go +++ b/util/metrics/metrics.go @@ -39,6 +39,14 @@ var ( NetworkMessageReceivedTotal = MetricName{Name: "algod_network_message_received_total", Description: "Total number of complete messages that were received from the network"} // NetworkMessageSentTotal Total number of complete messages that were sent to the network NetworkMessageSentTotal = MetricName{Name: "algod_network_message_sent_total", Description: "Total number of complete messages that were sent to the network"} + // NetworkP2PSentBytesTotal Total number of bytes that were sent over the p2p network + NetworkP2PSentBytesTotal = MetricName{Name: "algod_network_p2p_sent_bytes_total", Description: "Total number of bytes that were sent over the p2p network"} + // NetworkP2PReceivedBytesTotal Total number of bytes that were received from the p2p network + NetworkP2PReceivedBytesTotal = MetricName{Name: "algod_network_p2p_received_bytes_total", Description: "Total number of bytes that were received from the p2p network"} + // NetworkP2PMessageReceivedTotal Total number of complete messages that were received from the p2p network + NetworkP2PMessageReceivedTotal = MetricName{Name: "algod_network_p2p_message_received_total", Description: "Total number of complete messages that were received from the p2p network"} + // NetworkP2PMessageSentTotal Total number of complete messages that were sent to the p2p network + NetworkP2PMessageSentTotal = MetricName{Name: "algod_network_p2p_message_sent_total", Description: "Total number of complete messages that were sent to the p2p network"} // NetworkConnectionsDroppedTotal Total number of connections that were dropped before a message NetworkConnectionsDroppedTotal = MetricName{Name: "algod_network_connections_dropped_total", Description: "Total number of connections that were dropped before a message"} // NetworkSentDecompressedBytesTotal Total number of bytes that were sent over the network prior of being compressed From 10e8b3901d9b8573654c14d5a47c6cb235f04650 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:18:17 -0400 Subject: [PATCH 209/218] tests: fix a race in TestP2PwsStreamHandlerDedup (#6116) --- network/p2p/capabilities_test.go | 37 -------------------------------- network/p2pNetwork_test.go | 6 ++++-- 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/network/p2p/capabilities_test.go b/network/p2p/capabilities_test.go index 7057eca017..5e662c87fa 100644 --- a/network/p2p/capabilities_test.go +++ b/network/p2p/capabilities_test.go @@ -23,7 +23,6 @@ import ( "testing" "time" - golog "github.com/ipfs/go-log" "github.com/libp2p/go-libp2p" dht "github.com/libp2p/go-libp2p-kad-dht" "github.com/libp2p/go-libp2p/core/discovery" @@ -38,42 +37,6 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func TestCapabilities_Discovery(t *testing.T) { - partitiontest.PartitionTest(t) - - golog.SetDebugLogging() - var caps []*CapabilitiesDiscovery - var addrs []peer.AddrInfo - testSize := 3 - for i := 0; i < testSize; i++ { - tempdir := t.TempDir() - ps, err := peerstore.NewPeerStore(nil, "") - require.NoError(t, err) - h, _, err := MakeHost(config.GetDefaultLocal(), tempdir, ps) - require.NoError(t, err) - capD, err := MakeCapabilitiesDiscovery(context.Background(), config.GetDefaultLocal(), h, "devtestnet", logging.Base(), func() []peer.AddrInfo { return nil }) - require.NoError(t, err) - caps = append(caps, capD) - addrs = append(addrs, peer.AddrInfo{ - ID: capD.Host().ID(), - Addrs: capD.Host().Addrs(), - }) - } - for _, capD := range caps { - peersAdded := 0 - for _, addr := range addrs { - added, err := capD.addPeer(addr) - require.NoError(t, err) - require.True(t, added) - peersAdded++ - } - err := capD.dht.Bootstrap(context.Background()) - require.NoError(t, err) - capD.dht.ForceRefresh() - require.Equal(t, peersAdded, capD.dht.RoutingTable().Size()) - } -} - func setupDHTHosts(t *testing.T, numHosts int) []*dht.IpfsDHT { var hosts []host.Host var bootstrapPeers []peer.AddrInfo diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 302aa76147..e2c231f843 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -1187,8 +1187,10 @@ func TestP2PwsStreamHandlerDedup(t *testing.T) { return networkPeerIdentityDisconnect.GetUint64Value() == networkPeerIdentityDisconnectInitial+1 }, 2*time.Second, 50*time.Millisecond) - require.False(t, netA.hasPeers()) - require.False(t, netB.hasPeers()) + // now allow the peer made outgoing connection to handle conn closing initiated by the other side + require.Eventually(t, func() bool { + return !netA.hasPeers() && !netB.hasPeers() + }, 2*time.Second, 50*time.Millisecond) } // TestP2PEnableGossipService_NodeDisable ensures that a node with EnableGossipService=false From 81edd96c47ef9b004a3807fc9cc2816d52aa75d7 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:43:18 -0400 Subject: [PATCH 210/218] tests: use temp file for capturing e2e goal-partkey-commands output (#6115) --- .github/workflows/reviewdog.yml | 13 +++++++++++++ test/scripts/e2e_subs/goal-partkey-commands.sh | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index b02d99e7cc..7c0f2d26ae 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -102,3 +102,16 @@ jobs: run: | curl -X POST --data-urlencode "payload={\"text\": \"Reviewdog failed. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} \"}" $SLACK_WEBHOOK if: ${{ failure() && (contains(github.ref_name, 'rel/nightly') || contains(github.ref_name, 'rel/beta') || contains(github.ref_name, 'rel/stable') || contains(github.ref_name, 'master')) }} + reviewdog-shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: shellcheck + uses: reviewdog/action-shellcheck@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + reporter: "github-pr-check" + shellcheck_flags: "-e SC2034,SC2046,SC2053,SC2207,SC2145 -S warning" + fail_on_error: true + path: | + test/scripts/e2e_subs diff --git a/test/scripts/e2e_subs/goal-partkey-commands.sh b/test/scripts/e2e_subs/goal-partkey-commands.sh index b333a0e8aa..7d7154ec7b 100755 --- a/test/scripts/e2e_subs/goal-partkey-commands.sh +++ b/test/scripts/e2e_subs/goal-partkey-commands.sh @@ -83,16 +83,22 @@ verify_registered_state () { SEARCH_INVOKE_CONTEXT=$(echo "$3" | xargs) # look for participation ID anywhere in the partkeyinfo output - PARTKEY_OUTPUT=$(${gcmd} account partkeyinfo) - if ! echo "$PARTKEY_OUTPUT" | grep -q -F "$SEARCH_KEY"; then - fail_test "Key $SEARCH_KEY was not installed properly for cmd '$SEARCH_INVOKE_CONTEXT':\n$PARTKEY_OUTPUT" + info_temp_file=$(mktemp) + ${gcmd} account partkeyinfo > "${info_temp_file}" + if ! grep -q -F "$SEARCH_KEY" "${info_temp_file}"; then + echo "info_temp_file contents:" + cat "${info_temp_file}" + fail_test "Key $SEARCH_KEY was not installed properly for cmd '$SEARCH_INVOKE_CONTEXT'" fi # looking for yes/no, and the 8 character head of participation id in this line: # yes LFMT...RHJQ 4UPT6AQC... 4 0 3000 - LISTKEY_OUTPUT=$(${gcmd} account listpartkeys) - if ! echo "$LISTKEY_OUTPUT" | grep -q "$SEARCH_STATE.*$(echo "$SEARCH_KEY" | cut -c1-8)"; then - fail_test "Unexpected key $SEARCH_KEY state (looked for $SEARCH_STATE ) for cmd '$SEARCH_INVOKE_CONTEXT':\n$LISTKEY_OUTPUT" + list_temp_file=$(mktemp) + ${gcmd} account listpartkeys > "${list_temp_file}" + if ! grep -q "$SEARCH_STATE.*$(echo "$SEARCH_KEY" | cut -c1-8)" "${list_temp_file}"; then + echo "list_temp_file contents:" + cat "${list_temp_file}" + fail_test "Unexpected key $SEARCH_KEY state (looked for $SEARCH_STATE ) for cmd '$SEARCH_INVOKE_CONTEXT'" fi } From d105841e29f0b93aa74f3a757b60bc1c6297fd13 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:16:00 -0400 Subject: [PATCH 211/218] p2p: store private keys as PKCS#8 ASN.1 DER PEM (#6119) --- network/p2p/peerID.go | 45 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/network/p2p/peerID.go b/network/p2p/peerID.go index ca7526977b..c1b2536575 100644 --- a/network/p2p/peerID.go +++ b/network/p2p/peerID.go @@ -19,7 +19,10 @@ package p2p import ( + "crypto/ed25519" "crypto/rand" + "crypto/x509" + "encoding/pem" "fmt" "os" "path" @@ -34,7 +37,7 @@ import ( // DefaultPrivKeyPath is the default path inside the node's root directory at which the private key // for p2p identity is found and persisted to when a new one is generated. -const DefaultPrivKeyPath = "peerIDPrivKey.pem" +const DefaultPrivKeyPath = "peerIDPrivKey.key" // PeerID is a string representation of a peer's public key, primarily used to avoid importing libp2p into packages that shouldn't need it type PeerID string @@ -84,6 +87,9 @@ func PeerIDFromPublicKey(pubKey crypto.PubKey) (PeerID, error) { return PeerID(peerID), nil } +// pemBlockType is the type of PEM block used for private keys +const pemBlockType = "PRIVATE KEY" + // loadPrivateKeyFromFile attempts to read raw privKey bytes from path // It only supports Ed25519 keys. func loadPrivateKeyFromFile(path string) (crypto.PrivKey, error) { @@ -91,8 +97,21 @@ func loadPrivateKeyFromFile(path string) (crypto.PrivKey, error) { if err != nil { return nil, err } + p, _ := pem.Decode(bytes) + if p == nil || p.Type != pemBlockType { + return nil, fmt.Errorf("failed to PEM decode private key at %s", path) + } + + ak, err := x509.ParsePKCS8PrivateKey(p.Bytes) + if err != nil { + return nil, err + } + sk, ok := ak.(ed25519.PrivateKey) + if !ok { + return nil, fmt.Errorf("unsupported private key type: %T, expecting ed25519", ak) + } // We only support Ed25519 keys - return crypto.UnmarshalEd25519PrivateKey(bytes) + return crypto.UnmarshalEd25519PrivateKey(sk) } // writePrivateKeyToFile attempts to write raw privKey bytes to path @@ -101,7 +120,27 @@ func writePrivateKeyToFile(path string, privKey crypto.PrivKey) error { if err != nil { return err } - return os.WriteFile(path, bytes, 0600) + if len(bytes) != ed25519.PrivateKeySize { + return fmt.Errorf("incompatible ed25519 private key length: %d", len(bytes)) + } + key := ed25519.PrivateKey(bytes) + derBytes, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return err + } + + p := pem.Block{ + Type: pemBlockType, + Bytes: derBytes, + } + + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer f.Close() + + return pem.Encode(f, &p) } // generatePrivKey creates a new Ed25519 key From 0da0e99556ce4426902c6a31ba0ea30c00ed320f Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:16:15 -0400 Subject: [PATCH 212/218] network: fix outgoing HTTP rate limiting (#6118) --- network/limitcaller/rateLimitingTransport.go | 49 +++++++------ .../limitcaller/rateLimitingTransport_test.go | 72 +++++++++++++++++++ network/p2p/http.go | 6 +- network/p2pNetwork.go | 9 +-- network/p2pNetwork_test.go | 2 +- network/wsNetwork.go | 19 +++-- network/wsNetwork_test.go | 39 ++++++++++ 7 files changed, 155 insertions(+), 41 deletions(-) create mode 100644 network/limitcaller/rateLimitingTransport_test.go diff --git a/network/limitcaller/rateLimitingTransport.go b/network/limitcaller/rateLimitingTransport.go index de68c9b371..7877c879d1 100644 --- a/network/limitcaller/rateLimitingTransport.go +++ b/network/limitcaller/rateLimitingTransport.go @@ -22,7 +22,6 @@ import ( "time" "github.com/algorand/go-algorand/util" - "github.com/libp2p/go-libp2p/core/peer" ) // ConnectionTimeStore is a subset of the phonebook that is used to store the connection times. @@ -31,12 +30,12 @@ type ConnectionTimeStore interface { UpdateConnectionTime(addrOrPeerID string, provisionalTime time.Time) bool } -// RateLimitingTransport is the transport for execute a single HTTP transaction, obtaining the Response for a given Request. -type RateLimitingTransport struct { +// RateLimitingBoundTransport is the transport for execute a single HTTP transaction, obtaining the Response for a given Request. +type RateLimitingBoundTransport struct { phonebook ConnectionTimeStore innerTransport http.RoundTripper queueingTimeout time.Duration - targetAddr interface{} // target address for the p2p http request + addrOrPeerID string } // DefaultQueueingTimeout is the default timeout for queueing the request. @@ -46,9 +45,10 @@ const DefaultQueueingTimeout = 10 * time.Second // queueing the current request before the request attempt could be made. var ErrConnectionQueueingTimeout = errors.New("rateLimitingTransport: queueing timeout") -// MakeRateLimitingTransport creates a rate limiting http transport that would limit the requests rate -// according to the entries in the phonebook. -func MakeRateLimitingTransport(phonebook ConnectionTimeStore, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int) RateLimitingTransport { +// MakeRateLimitingBoundTransport creates a rate limiting http transport that that: +// 1. would limit the requests rate according to the entries in the phonebook. +// 2. is bound to a specific target. +func MakeRateLimitingBoundTransport(phonebook ConnectionTimeStore, queueingTimeout time.Duration, dialer *Dialer, maxIdleConnsPerHost int, target string) RateLimitingBoundTransport { defaultTransport := http.DefaultTransport.(*http.Transport) innerTransport := &http.Transport{ Proxy: defaultTransport.Proxy, @@ -59,37 +59,36 @@ func MakeRateLimitingTransport(phonebook ConnectionTimeStore, queueingTimeout ti ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout, MaxIdleConnsPerHost: maxIdleConnsPerHost, } - return MakeRateLimitingTransportWithRoundTripper(phonebook, queueingTimeout, innerTransport, nil, maxIdleConnsPerHost) + return MakeRateLimitingBoundTransportWithRoundTripper(phonebook, queueingTimeout, innerTransport, target) } -// MakeRateLimitingTransportWithRoundTripper creates a rate limiting http transport that would limit the requests rate -// according to the entries in the phonebook. -func MakeRateLimitingTransportWithRoundTripper(phonebook ConnectionTimeStore, queueingTimeout time.Duration, rt http.RoundTripper, target interface{}, maxIdleConnsPerHost int) RateLimitingTransport { - return RateLimitingTransport{ +// MakeRateLimitingBoundTransportWithRoundTripper creates a rate limiting http transport that: +// 1. would limit the requests rate according to the entries in the phonebook. +// 2. is bound to a specific target. +func MakeRateLimitingBoundTransportWithRoundTripper(phonebook ConnectionTimeStore, queueingTimeout time.Duration, rt http.RoundTripper, target string) RateLimitingBoundTransport { + return RateLimitingBoundTransport{ phonebook: phonebook, innerTransport: rt, queueingTimeout: queueingTimeout, - targetAddr: target, + addrOrPeerID: target, } } // RoundTrip connects to the address on the named network using the provided context. // It waits if needed not to exceed connectionsRateLimitingCount. -func (r *RateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response, err error) { +func (r *RateLimitingBoundTransport) RoundTrip(req *http.Request) (res *http.Response, err error) { var waitTime time.Duration var provisionalTime time.Time - queueingDeadline := time.Now().Add(r.queueingTimeout) - addrOrPeerID := req.Host - // p2p/http clients have per-connection transport and address info so use that - if len(req.Host) == 0 && req.URL != nil && len(req.URL.Host) == 0 { - addrInfo, ok := r.targetAddr.(*peer.AddrInfo) - if !ok { - return nil, errors.New("rateLimitingTransport: request without Host/URL and targetAddr is not a peer.AddrInfo") - } - addrOrPeerID = string(addrInfo.ID) + if r.addrOrPeerID == "" { + return nil, errors.New("rateLimitingTransport: target not set") + } + if req.URL != nil && req.URL.Host != "" && req.URL.Host != r.addrOrPeerID { + return nil, errors.New("rateLimitingTransport: request URL host does not match the target") } + + queueingDeadline := time.Now().Add(r.queueingTimeout) for { - _, waitTime, provisionalTime = r.phonebook.GetConnectionWaitTime(addrOrPeerID) + _, waitTime, provisionalTime = r.phonebook.GetConnectionWaitTime(r.addrOrPeerID) if waitTime == 0 { break // break out of the loop and proceed to the connection } @@ -101,6 +100,6 @@ func (r *RateLimitingTransport) RoundTrip(req *http.Request) (res *http.Response return nil, ErrConnectionQueueingTimeout } res, err = r.innerTransport.RoundTrip(req) - r.phonebook.UpdateConnectionTime(addrOrPeerID, provisionalTime) + r.phonebook.UpdateConnectionTime(r.addrOrPeerID, provisionalTime) return } diff --git a/network/limitcaller/rateLimitingTransport_test.go b/network/limitcaller/rateLimitingTransport_test.go new file mode 100644 index 0000000000..155ed8310f --- /dev/null +++ b/network/limitcaller/rateLimitingTransport_test.go @@ -0,0 +1,72 @@ +// Copyright (C) 2019-2024 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 limitcaller + +import ( + "net/http" + "testing" + "time" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +type ctStore struct { + t *testing.T + getCnt uint64 +} + +func (c *ctStore) GetConnectionWaitTime(addrOrPeerID string) (bool, time.Duration, time.Time) { + require.NotEmpty(c.t, addrOrPeerID) + c.getCnt++ + return false, 0, time.Time{} +} + +func (c *ctStore) UpdateConnectionTime(addrOrPeerID string, provisionalTime time.Time) bool { + require.NotEmpty(c.t, addrOrPeerID) + return false +} + +type emptyRoundTripper struct{} + +func (e *emptyRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { return nil, nil } + +func TestRoundTrip(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ctStore := ctStore{t: t} + rtt := MakeRateLimitingBoundTransportWithRoundTripper(&ctStore, 0, &emptyRoundTripper{}, "") + req := &http.Request{} + _, err := rtt.RoundTrip(req) + require.ErrorContains(t, err, "target not set") + require.Equal(t, uint64(0), ctStore.getCnt) + + rtt = MakeRateLimitingBoundTransportWithRoundTripper(&ctStore, 0, &emptyRoundTripper{}, "mytarget") + req, err = http.NewRequest("GET", "https://example.com/test", nil) + require.NoError(t, err) + _, err = rtt.RoundTrip(req) + require.ErrorContains(t, err, "URL host does not match the target") + require.Equal(t, uint64(0), ctStore.getCnt) + + rtt = MakeRateLimitingBoundTransportWithRoundTripper(&ctStore, 0, &emptyRoundTripper{}, "mytarget") + req, err = http.NewRequest("GET", "/test", nil) + require.NoError(t, err) + _, err = rtt.RoundTrip(req) + require.NoError(t, err) + require.Equal(t, uint64(1), ctStore.getCnt) +} diff --git a/network/p2p/http.go b/network/p2p/http.go index 07f27afff1..633a13713d 100644 --- a/network/p2p/http.go +++ b/network/p2p/http.go @@ -88,13 +88,13 @@ func MakeHTTPClient(addrInfo *peer.AddrInfo) (*http.Client, error) { } // MakeHTTPClientWithRateLimit creates a http.Client that uses libp2p transport for a given protocol and peer address. -func MakeHTTPClientWithRateLimit(addrInfo *peer.AddrInfo, pstore limitcaller.ConnectionTimeStore, queueingTimeout time.Duration, maxIdleConnsPerHost int) (*http.Client, error) { +func MakeHTTPClientWithRateLimit(addrInfo *peer.AddrInfo, pstore limitcaller.ConnectionTimeStore, queueingTimeout time.Duration) (*http.Client, error) { cl, err := MakeHTTPClient(addrInfo) if err != nil { return nil, err } - rlrt := limitcaller.MakeRateLimitingTransportWithRoundTripper(pstore, queueingTimeout, cl.Transport, addrInfo, maxIdleConnsPerHost) - cl.Transport = &rlrt + rltr := limitcaller.MakeRateLimitingBoundTransportWithRoundTripper(pstore, queueingTimeout, cl.Transport, string(addrInfo.ID)) + cl.Transport = &rltr return cl, nil } diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index d3af60a223..f88660b653 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -613,8 +613,7 @@ func addrInfoToWsPeerCore(n *P2PNetwork, addrInfo *peer.AddrInfo) (wsPeerCore, b } addr := mas[0].String() - maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) - client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) + client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout) if err != nil { n.log.Warnf("MakeHTTPClient failed: %v", err) return wsPeerCore{}, false @@ -720,8 +719,7 @@ func (n *P2PNetwork) GetHTTPClient(address string) (*http.Client, error) { if err != nil { return nil, err } - maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) - return p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) + return p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout) } // OnNetworkAdvance notifies the network library that the agreement protocol was able to make a notable progress. @@ -774,8 +772,7 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea // create a wsPeer for this stream and added it to the peers map. addrInfo := &peer.AddrInfo{ID: p2pPeer, Addrs: []multiaddr.Multiaddr{ma}} - maxIdleConnsPerHost := int(n.config.ConnectionsRateLimitingCount) - client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout, maxIdleConnsPerHost) + client, err := p2p.MakeHTTPClientWithRateLimit(addrInfo, n.pstore, limitcaller.DefaultQueueingTimeout) if err != nil { n.log.Warnf("Cannot construct HTTP Client for %s: %v", p2pPeer, err) client = nil diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index e2c231f843..dcc641c350 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -783,7 +783,7 @@ func TestP2PHTTPHandler(t *testing.T) { pstore, err := peerstore.MakePhonebook(0, 10*time.Second) require.NoError(t, err) pstore.AddPersistentPeers([]*peer.AddrInfo{&peerInfoA}, "net", phonebook.PhoneBookEntryRelayRole) - httpClient, err = p2p.MakeHTTPClientWithRateLimit(&peerInfoA, pstore, 1*time.Second, 1) + httpClient, err = p2p.MakeHTTPClientWithRateLimit(&peerInfoA, pstore, 1*time.Second) require.NoError(t, err) _, err = httpClient.Get("/test") require.ErrorIs(t, err, limitcaller.ErrConnectionQueueingTimeout) diff --git a/network/wsNetwork.go b/network/wsNetwork.go index ecb636c8e2..c67200f01b 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -230,10 +230,9 @@ type WebsocketNetwork struct { // number of throttled outgoing connections "slots" needed to be populated. throttledOutgoingConnections atomic.Int32 - // transport and dialer are customized to limit the number of + // dialer is customized to limit the number of // connection in compliance with connectionsRateLimitingCount. - transport limitcaller.RateLimitingTransport - dialer limitcaller.Dialer + dialer limitcaller.Dialer // messagesOfInterest specifies the message types that this node // wants to receive. nil means default. non-nil causes this @@ -565,9 +564,7 @@ func (wn *WebsocketNetwork) setup() { if wn.nodeInfo == nil { wn.nodeInfo = &nopeNodeInfo{} } - maxIdleConnsPerHost := int(wn.config.ConnectionsRateLimitingCount) wn.dialer = limitcaller.MakeRateLimitingDialer(wn.phonebook, preferredResolver) - wn.transport = limitcaller.MakeRateLimitingTransport(wn.phonebook, limitcaller.DefaultQueueingTimeout, &wn.dialer, maxIdleConnsPerHost) wn.upgrader.ReadBufferSize = 4096 wn.upgrader.WriteBufferSize = 4096 @@ -1975,8 +1972,18 @@ func (wn *WebsocketNetwork) numOutgoingPending() int { // GetHTTPClient returns a http.Client with a suitable for the network Transport // that would also limit the number of outgoing connections. func (wn *WebsocketNetwork) GetHTTPClient(address string) (*http.Client, error) { + url, err := addr.ParseHostOrURL(address) + if err != nil { + return nil, err + } + + maxIdleConnsPerHost := int(wn.config.ConnectionsRateLimitingCount) + rltr := limitcaller.MakeRateLimitingBoundTransport(wn.phonebook, limitcaller.DefaultQueueingTimeout, &wn.dialer, maxIdleConnsPerHost, url.Host) return &http.Client{ - Transport: &HTTPPAddressBoundTransport{address, &wn.transport}, + Transport: &HTTPPAddressBoundTransport{ + address, + &rltr, + }, }, nil } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 0128c28fc2..91983dfa20 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -4601,3 +4601,42 @@ func TestHTTPPAddressBoundTransport(t *testing.T) { } } } + +// TestWebsocketNetworkHTTPClient checks ws net HTTP client can connect to another node +// with out unexpected errors +func TestWebsocketNetworkHTTPClient(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + netA := makeTestWebsocketNode(t) + err := netA.Start() + require.NoError(t, err) + defer netStop(t, netA, "A") + + netB := makeTestWebsocketNodeWithConfig(t, defaultConfig) + + addr, ok := netA.Address() + require.True(t, ok) + + c, err := netB.GetHTTPClient(addr) + require.NoError(t, err) + + netA.RegisterHTTPHandlerFunc("/handled", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + resp, err := c.Do(&http.Request{URL: &url.URL{Path: "/handled"}}) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + resp, err = c.Do(&http.Request{URL: &url.URL{Path: "/test"}}) + require.NoError(t, err) + require.Equal(t, http.StatusNotFound, resp.StatusCode) // no such handler + + resp, err = c.Do(&http.Request{URL: &url.URL{Path: "/v1/" + genesisID + "/gossip"}}) + require.NoError(t, err) + require.Equal(t, http.StatusPreconditionFailed, resp.StatusCode) // not enough ws peer headers + + _, err = netB.GetHTTPClient("invalid") + require.Error(t, err) +} From 43cb563248776b2db0743e9c0579e8c8e83cbf7c Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 3 Sep 2024 17:06:00 -0400 Subject: [PATCH 213/218] AVM: Provide access to some more block header values (#6107) --- data/transactions/logic/TEAL_opcodes_v10.md | 2 +- data/transactions/logic/TEAL_opcodes_v7.md | 2 +- data/transactions/logic/TEAL_opcodes_v8.md | 2 +- data/transactions/logic/TEAL_opcodes_v9.md | 2 +- data/transactions/logic/assembler_test.go | 10 +++++ data/transactions/logic/eval.go | 12 ++++++ data/transactions/logic/fields.go | 17 +++++++- data/transactions/logic/fields_string.go | 11 +++-- data/transactions/logic/langspec_v10.json | 2 +- data/transactions/logic/langspec_v7.json | 2 +- data/transactions/logic/langspec_v8.json | 2 +- data/transactions/logic/langspec_v9.json | 2 +- test/scripts/e2e_subs/hdr-access.py | 46 ++++++++++++++------- 13 files changed, 86 insertions(+), 26 deletions(-) diff --git a/data/transactions/logic/TEAL_opcodes_v10.md b/data/transactions/logic/TEAL_opcodes_v10.md index b90801521b..4e4491e095 100644 --- a/data/transactions/logic/TEAL_opcodes_v10.md +++ b/data/transactions/logic/TEAL_opcodes_v10.md @@ -1638,7 +1638,7 @@ Fields | Index | Name | Type | Notes | | - | ------ | -- | --------- | -| 0 | BlkSeed | []byte | | +| 0 | BlkSeed | [32]byte | | | 1 | BlkTimestamp | uint64 | | diff --git a/data/transactions/logic/TEAL_opcodes_v7.md b/data/transactions/logic/TEAL_opcodes_v7.md index 3a0c678b00..74314af059 100644 --- a/data/transactions/logic/TEAL_opcodes_v7.md +++ b/data/transactions/logic/TEAL_opcodes_v7.md @@ -1476,6 +1476,6 @@ Fields | Index | Name | Type | Notes | | - | ------ | -- | --------- | -| 0 | BlkSeed | []byte | | +| 0 | BlkSeed | [32]byte | | | 1 | BlkTimestamp | uint64 | | diff --git a/data/transactions/logic/TEAL_opcodes_v8.md b/data/transactions/logic/TEAL_opcodes_v8.md index f06e087e5e..a1059bc50e 100644 --- a/data/transactions/logic/TEAL_opcodes_v8.md +++ b/data/transactions/logic/TEAL_opcodes_v8.md @@ -1635,6 +1635,6 @@ Fields | Index | Name | Type | Notes | | - | ------ | -- | --------- | -| 0 | BlkSeed | []byte | | +| 0 | BlkSeed | [32]byte | | | 1 | BlkTimestamp | uint64 | | diff --git a/data/transactions/logic/TEAL_opcodes_v9.md b/data/transactions/logic/TEAL_opcodes_v9.md index e14f4d7d76..ac4482ce3e 100644 --- a/data/transactions/logic/TEAL_opcodes_v9.md +++ b/data/transactions/logic/TEAL_opcodes_v9.md @@ -1635,6 +1635,6 @@ Fields | Index | Name | Type | Notes | | - | ------ | -- | --------- | -| 0 | BlkSeed | []byte | | +| 0 | BlkSeed | [32]byte | | | 1 | BlkTimestamp | uint64 | | diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 32101dbcbc..10d86f476a 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1700,11 +1700,21 @@ global AssetCreateMinBalance global AssetOptInMinBalance global GenesisHash pushint 1 +block BlkBranch +pushint 1 +block BlkFeeSink +pushint 1 +block BlkProtocol +pushint 1 +block BlkTxnCounter +pushint 1 block BlkProposer pushint 1 block BlkFeesCollected pushint 1 block BlkBonus +pushint 1 +block BlkProposerPayout global PayoutsEnabled global PayoutsGoOnlineFee global PayoutsPercent diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 3201877ed9..4da436a1b6 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -5771,12 +5771,24 @@ func opBlock(cx *EvalContext) error { return fmt.Errorf("block(%d) timestamp %d < 0", round, hdr.TimeStamp) } cx.Stack[last] = stackValue{Uint: uint64(hdr.TimeStamp)} + + case BlkBranch: + cx.Stack[last].Bytes = hdr.Branch[:] + case BlkFeeSink: + cx.Stack[last].Bytes = hdr.FeeSink[:] + case BlkProtocol: + cx.Stack[last].Bytes = []byte(hdr.CurrentProtocol) + case BlkTxnCounter: + cx.Stack[last] = stackValue{Uint: hdr.TxnCounter} + case BlkProposer: cx.Stack[last].Bytes = hdr.Proposer[:] case BlkFeesCollected: cx.Stack[last] = stackValue{Uint: hdr.FeesCollected.Raw} case BlkBonus: cx.Stack[last] = stackValue{Uint: hdr.Bonus.Raw} + case BlkProposerPayout: + cx.Stack[last] = stackValue{Uint: hdr.ProposerPayout.Raw} default: return fmt.Errorf("invalid block field %s", fs.field) } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 99cc08bad2..b2f384c259 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -997,6 +997,16 @@ const ( BlkFeesCollected // BlkBonus is the extra amount to be paid for the given block (from FeeSink) BlkBonus + // BlkBranch is the hash of the previous block + BlkBranch + // BlkFeeSink is the fee sink for the given round + BlkFeeSink + // BlkProtocol is the ConsensusVersion of the block. + BlkProtocol + // BlkTxnCounter is the number of the next transaction after the block + BlkTxnCounter + // BlkProposerPayout is the actual amount moved from feesink to proposer + BlkProposerPayout invalidBlockField // compile-time constant for number of fields ) @@ -1010,11 +1020,16 @@ type blockFieldSpec struct { } var blockFieldSpecs = [...]blockFieldSpec{ - {BlkSeed, StackBytes, randomnessVersion}, + {BlkSeed, StackBytes32, randomnessVersion}, {BlkTimestamp, StackUint64, randomnessVersion}, {BlkProposer, StackAddress, incentiveVersion}, {BlkFeesCollected, StackUint64, incentiveVersion}, {BlkBonus, StackUint64, incentiveVersion}, + {BlkBranch, StackBytes32, incentiveVersion}, + {BlkFeeSink, StackAddress, incentiveVersion}, + {BlkProtocol, StackBytes, incentiveVersion}, + {BlkTxnCounter, StackUint64, incentiveVersion}, + {BlkProposerPayout, StackUint64, incentiveVersion}, } func blockFieldSpecByField(r BlockField) (blockFieldSpec, bool) { diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 3463da269c..df9922abf2 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -382,12 +382,17 @@ func _() { _ = x[BlkProposer-2] _ = x[BlkFeesCollected-3] _ = x[BlkBonus-4] - _ = x[invalidBlockField-5] + _ = x[BlkBranch-5] + _ = x[BlkFeeSink-6] + _ = x[BlkProtocol-7] + _ = x[BlkTxnCounter-8] + _ = x[BlkProposerPayout-9] + _ = x[invalidBlockField-10] } -const _BlockField_name = "BlkSeedBlkTimestampBlkProposerBlkFeesCollectedBlkBonusinvalidBlockField" +const _BlockField_name = "BlkSeedBlkTimestampBlkProposerBlkFeesCollectedBlkBonusBlkBranchBlkFeeSinkBlkProtocolBlkTxnCounterBlkProposerPayoutinvalidBlockField" -var _BlockField_index = [...]uint8{0, 7, 19, 30, 46, 54, 71} +var _BlockField_index = [...]uint8{0, 7, 19, 30, 46, 54, 63, 73, 84, 97, 114, 131} func (i BlockField) String() string { if i < 0 || i >= BlockField(len(_BlockField_index)-1) { diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json index f59103f0f6..1a8986436a 100644 --- a/data/transactions/logic/langspec_v10.json +++ b/data/transactions/logic/langspec_v10.json @@ -4601,7 +4601,7 @@ "BlkTimestamp" ], "ArgEnumTypes": [ - "[]byte", + "[32]byte", "uint64" ], "DocCost": "1", diff --git a/data/transactions/logic/langspec_v7.json b/data/transactions/logic/langspec_v7.json index a7b4df95b3..12d2594194 100644 --- a/data/transactions/logic/langspec_v7.json +++ b/data/transactions/logic/langspec_v7.json @@ -4249,7 +4249,7 @@ "BlkTimestamp" ], "ArgEnumTypes": [ - "[]byte", + "[32]byte", "uint64" ], "DocCost": "1", diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json index 186f9cdfe2..c5fcdbf58d 100644 --- a/data/transactions/logic/langspec_v8.json +++ b/data/transactions/logic/langspec_v8.json @@ -4595,7 +4595,7 @@ "BlkTimestamp" ], "ArgEnumTypes": [ - "[]byte", + "[32]byte", "uint64" ], "DocCost": "1", diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json index cab0aa6d91..01e951cc3c 100644 --- a/data/transactions/logic/langspec_v9.json +++ b/data/transactions/logic/langspec_v9.json @@ -4595,7 +4595,7 @@ "BlkTimestamp" ], "ArgEnumTypes": [ - "[]byte", + "[32]byte", "uint64" ], "DocCost": "1", diff --git a/test/scripts/e2e_subs/hdr-access.py b/test/scripts/e2e_subs/hdr-access.py index bb6c0ad650..32739e91b2 100755 --- a/test/scripts/e2e_subs/hdr-access.py +++ b/test/scripts/e2e_subs/hdr-access.py @@ -1,8 +1,10 @@ #!/usr/bin/env python +import base64 import os import sys from goal import Goal +import algosdk.encoding as enc from datetime import datetime @@ -43,13 +45,10 @@ txinfo, err = goal.app_create(joe, goal.assemble(teal)) assert "not available" in str(err), err -# We want to manipulate lastvalid, so we need to turn off autosend -goal.autosend = False - -# We will be able to access two blocks, by setting lv explcitly. So we -# test that the block timestamp from two blocks ago is between 2 and 5 -# (inclusive) seconds before the previous block timestamp. devMode -# might mess this test up. +# We will be able to access more than one previous block by using a +# shorter tx liftetime. So we test that the block timestamp from two +# blocks ago is between 2 and 5 (inclusive) seconds before the +# previous block timestamp. devMode might mess this test up. teal = """ #pragma version 7 txn FirstValid @@ -73,10 +72,7 @@ int 6 < """ -checktimes = goal.assemble(teal) -tx = goal.app_create(joe, goal.assemble(teal)) -tx.last_valid_round = tx.last_valid_round - 800 -txinfo, err = goal.send(tx) +txinfo, err = goal.app_create(joe, goal.assemble(teal), lifetime=100) assert not err, err # block 0 is not accessible even with a low LastValid @@ -85,11 +81,33 @@ int 0 block BlkTimestamp """ -tx = goal.app_create(joe, goal.assemble(teal)) -tx.last_valid_round = tx.last_valid_round - 800 -txinfo, err = goal.send(tx) +txinfo, err = goal.app_create(joe, goal.assemble(teal), lifetime=100) assert "round 0 is not available" in str(err), err assert "outside [1-" in str(err), err # confirms that we can look back to 1 + +# Get FeeSink from `block` opcode, compare to REST API +teal = """ +#pragma version 11 + txn FirstValid + int 2 + - + block BlkFeeSink + log + int 1 + return +""" +txinfo, err = goal.app_create(joe, goal.assemble(teal), lifetime=100) +assert not err, err +assert len(txinfo["logs"]) == 1 +opcode = txinfo["logs"][0] + +block = goal.algod.block_info(txinfo['confirmed-round']-2)['block'] +api = base64.b64encode(enc.decode_address(block['fees'])).decode("utf-8") + +print(opcode, api) + +assert opcode == api + stamp = datetime.now().strftime("%Y%m%d_%H%M%S") print(f"{os.path.basename(sys.argv[0])} OK {stamp}") From b1d81bce7acc1db7767319f758d8b2c62acb23e6 Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 3 Sep 2024 17:06:28 -0400 Subject: [PATCH 214/218] algons: expose error for dnsaddr command (#6121) --- cmd/algons/dnsaddrCmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/algons/dnsaddrCmd.go b/cmd/algons/dnsaddrCmd.go index c8fb1ac52b..b248c867f2 100644 --- a/cmd/algons/dnsaddrCmd.go +++ b/cmd/algons/dnsaddrCmd.go @@ -141,7 +141,7 @@ var dnsaddrTreeCreateCmd = &cobra.Command{ dnsaddrsFrom := []string{fmt.Sprintf("_dnsaddr.%s", dnsaddrDomain)} entries, err := getEntries(dnsaddrsFrom[0], "TXT") if err != nil { - fmt.Printf("failed fetching entries for %s\n", dnsaddrsFrom[0]) + fmt.Printf("failed fetching entries for %s: %v\n", dnsaddrsFrom[0], err) os.Exit(1) } if len(entries) > 0 { From 66b87dc781a265cf1287aed7c6c714e43fcd4280 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:06:43 -0400 Subject: [PATCH 215/218] tests: fix expect tests and add EOF linter (#6122) Co-authored-by: cce <51567+cce@users.noreply.github.com> --- Makefile | 3 + scripts/travis/codegen_verification.sh | 3 + .../cli/goal/expect/basicExpectTest.exp | 4 +- test/e2e-go/cli/goal/expect/corsTest.exp | 1 + test/e2e-go/cli/goal/expect/expect_linter.py | 72 ++++++++++++++++ .../cli/goal/expect/goalCmdFlagsTest.exp | 1 + .../cli/goal/expect/goalDryrunRestTest.exp | 1 + .../cli/goal/expect/goalExpectCommon.exp | 85 ++++++++++++++----- .../cli/goal/expect/goalTxValidityTest.exp | 1 + .../e2e-go/cli/goal/expect/limitOrderTest.exp | 12 ++- .../goal/expect/tealAndStatefulTealTest.exp | 4 +- .../cli/goal/expect/tealConsensusTest.exp | 8 +- test/e2e-go/cli/goal/expect/testInfraTest.exp | 2 + 13 files changed, 166 insertions(+), 31 deletions(-) create mode 100644 test/e2e-go/cli/goal/expect/expect_linter.py diff --git a/Makefile b/Makefile index 52c505b301..b6823e665a 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,9 @@ fix: build lint: deps $(GOPATH1)/bin/golangci-lint run -c .golangci.yml +expectlint: + cd test/e2e-go/cli/goal/expect && python3 expect_linter.py *.exp + check_go_version: @if [ $(CURRENT_GO_VERSION_MAJOR) != $(GOLANG_VERSION_BUILD_MAJOR) ]; then \ echo "Wrong major version of Go installed ($(CURRENT_GO_VERSION_MAJOR)). Please use $(GOLANG_VERSION_BUILD_MAJOR)"; \ diff --git a/scripts/travis/codegen_verification.sh b/scripts/travis/codegen_verification.sh index 53ad607a49..5e3a53de3b 100755 --- a/scripts/travis/codegen_verification.sh +++ b/scripts/travis/codegen_verification.sh @@ -40,6 +40,9 @@ echo "Running fixcheck" GOPATH=$(go env GOPATH) "$GOPATH"/bin/algofix -error */ +echo "Running expect linter" +make expectlint + echo "Updating TEAL Specs" touch data/transactions/logic/fields_string.go # ensure rebuild make -C data/transactions/logic diff --git a/test/e2e-go/cli/goal/expect/basicExpectTest.exp b/test/e2e-go/cli/goal/expect/basicExpectTest.exp index d44f15ca65..ab572ba63e 100644 --- a/test/e2e-go/cli/goal/expect/basicExpectTest.exp +++ b/test/e2e-go/cli/goal/expect/basicExpectTest.exp @@ -16,12 +16,14 @@ if { [catch { spawn echo "hello" expect { timeout { abort "\n Failed to see expected input hello" } + eof { abort "Ended without hello" } "^hello*" {close} } spawn echo "goodbye" expect { timeout { abort "Failed to see expected input goodbye" } + eof { abort "Ended without goodbye" } "^goodbye*" {close} } @@ -29,4 +31,4 @@ if { [catch { } EXCEPTION ] } { abort "ERROR in basic expect test: $EXCEPTION" -} \ No newline at end of file +} diff --git a/test/e2e-go/cli/goal/expect/corsTest.exp b/test/e2e-go/cli/goal/expect/corsTest.exp index 7691b740fa..43fb0fa24d 100755 --- a/test/e2e-go/cli/goal/expect/corsTest.exp +++ b/test/e2e-go/cli/goal/expect/corsTest.exp @@ -32,6 +32,7 @@ if { [catch { ::AlgorandGoal::CheckNetworkAddressForCors $ALGOD_NET_ADDRESS # Start kmd, then do the same CORS check as algod + exec -- cat "$TEST_PRIMARY_NODE_DIR/kmd-v0.5/kmd_config.json.example" | jq {. |= . + {"allowed_origins": ["http://algorand.com"]}} > "$TEST_PRIMARY_NODE_DIR/kmd-v0.5/kmd_config.json" exec goal kmd start -t 180 -d $TEST_PRIMARY_NODE_DIR set KMD_NET_ADDRESS [::AlgorandGoal::GetKMDNetworkAddress $TEST_PRIMARY_NODE_DIR] ::AlgorandGoal::CheckNetworkAddressForCors $KMD_NET_ADDRESS diff --git a/test/e2e-go/cli/goal/expect/expect_linter.py b/test/e2e-go/cli/goal/expect/expect_linter.py new file mode 100644 index 0000000000..fdeb741148 --- /dev/null +++ b/test/e2e-go/cli/goal/expect/expect_linter.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +import sys +import argparse + +found_issues = False + + +def check_expect_blocks(filename, verbose=False): + with open(filename, 'r') as f: + lines = f.readlines() + + in_expect_block = False + brace_count = 0 + block_start_line = None + current_block = [] + expect_blocks = [] + + # Process each line, considering possible strings or comments + for line_num, line in enumerate(lines, start=1): + stripped_line = line.strip() + + if not in_expect_block: + if "expect " in stripped_line and '{' in stripped_line: + in_expect_block = True + block_start_line = line_num + brace_count = stripped_line.count('{') - stripped_line.count('}') + current_block = [stripped_line] + elif stripped_line.startswith("#") or stripped_line.startswith("//"): + continue # Ignore comment lines outside of expect blocks + else: + current_block.append(stripped_line) + brace_count += stripped_line.count('{') - stripped_line.count('}') + + if brace_count == 0: + in_expect_block = False + expect_blocks.append((block_start_line, "\n".join(current_block))) + current_block = [] + + for block_start_line, block in expect_blocks: + if '#nolint:eof' in block: + if verbose: + print(f"{filename}:{block_start_line}: SKIP: 'nolint:eof' comment found, skipping") + continue + + if 'eof ' not in block: + # Check for only timeout condition + actions = block.count('}') + if block.count('timeout') == actions: + if verbose: + print(f"{filename}:{block_start_line}: OK: only timeout action present") + continue + + print(f"{filename}:{block_start_line}: Warning: missing 'eof' in expect block") + global found_issues + found_issues = True + elif verbose: + print(f"{filename}:{block_start_line}: OK: expect block contains 'eof'") + +def main(): + parser = argparse.ArgumentParser(description="Check for 'eof' in expect blocks of scripts.") + parser.add_argument('files', metavar='FILE', type=str, nargs='+', help='Files to check') + parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output') + args = parser.parse_args() + + for fname in args.files: + check_expect_blocks(fname, args.verbose) + + if found_issues: + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/test/e2e-go/cli/goal/expect/goalCmdFlagsTest.exp b/test/e2e-go/cli/goal/expect/goalCmdFlagsTest.exp index d86cef47b4..2b833415b0 100644 --- a/test/e2e-go/cli/goal/expect/goalCmdFlagsTest.exp +++ b/test/e2e-go/cli/goal/expect/goalCmdFlagsTest.exp @@ -6,6 +6,7 @@ proc TestGoalCommandLineFlags { CMD EXPECTED_RE } { set PASSED 0 eval spawn $CMD expect { + #nolint:eof checking PASSED catches no match timeout { puts "goal asset create timed out"; exit 1 } -re $EXPECTED_RE {set PASSED 1; close } } diff --git a/test/e2e-go/cli/goal/expect/goalDryrunRestTest.exp b/test/e2e-go/cli/goal/expect/goalDryrunRestTest.exp index 26a2b24e11..3227d7fdd9 100644 --- a/test/e2e-go/cli/goal/expect/goalDryrunRestTest.exp +++ b/test/e2e-go/cli/goal/expect/goalDryrunRestTest.exp @@ -8,6 +8,7 @@ proc TestGoalDryrun { DRREQ_FILE TEST_PRIMARY_NODE_DIR } { set PROGRAM_TYPE "" spawn goal clerk dryrun-remote -d $TEST_PRIMARY_NODE_DIR -D $DRREQ_FILE -v expect { + #nolint:eof checking PASSED catches no match timeout { ::AlgorandGoal::Abort "goal clerk dryrun-remote timeout" } "budget consumed:" {set COST 1; exp_continue} -re {(ApprovalProgram)} {set PROGRAM_TYPE $expect_out(1,string); exp_continue} diff --git a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp index 4728f445df..1ee45ed033 100644 --- a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp +++ b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp @@ -234,12 +234,14 @@ proc ::AlgorandGoal::RestartNode { TEST_ALGO_DIR {SYSTEMD_MANAGED ""} } { expect { timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node restart" } "^The node was successfully stopped.*Algorand node successfully started!*" {puts "Node restarted successfully"; close} + eof { close; ::AlgorandGoal::Abort "Did not receive appropriate message before node restart eof" } } } else { spawn goal node restart -d $TEST_ALGO_DIR expect { timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node restart" } "^This node is using systemd and should be managed with systemctl*" { puts "Goal showed correct error message for systemd" ; close} + eof { close; ::AlgorandGoal::Abort "Did not receive appropriate message before node restart eof" } } } } EXCEPTION] } { @@ -333,22 +335,27 @@ proc ::AlgorandGoal::CreateWallet { WALLET_NAME WALLET_PASSWORD TEST_PRIMARY_NOD expect { timeout {::AlgorandGoal::Abort "Timed out CreateWallet password" } + eof {::AlgorandGoal::Abort "EOF CreateWallet password" } "Please choose a password for wallet*" { send "$WALLET_PASSWORD\r" } } expect { timeout {::AlgorandGoal::Abort "Timed out CreateWallet confirmation" } + eof {::AlgorandGoal::Abort "EOF CreateWallet confirmation" } "Please confirm*" { send "$WALLET_PASSWORD\r"} } expect { timeout {::AlgorandGoal::Abort "Timed out CreateWallet see it now" } + eof {::AlgorandGoal::Abort "EOF CreateWallet see it now" } "Would you like to see it now? (Y/n):" { send "y\r" } } expect { timeout {::AlgorandGoal::Abort "Timed out CreateWallet keep info safe" } + eof {::AlgorandGoal::Abort "EOF CreateWallet keep info safe" } "Keep this information safe -- never share it with anyone!" {} } expect { timeout {::AlgorandGoal::Abort "Timed out CreateWallet pass phrase" } + eof {::AlgorandGoal::Abort "EOF CreateWallet pass phrase" } -re {([a-z ]+)} {set WALLET_PASS_PHRASE $expect_out(1,string); close;} } } EXCEPTION ] } { @@ -364,6 +371,7 @@ proc ::AlgorandGoal::VerifyWallet { WALLET_NAME TEST_PRIMARY_NODE_DIR } { spawn goal wallet list -d $TEST_PRIMARY_NODE_DIR expect { timeout { ::AlgorandGoal::Abort "Timed out seeing expected input for spawn goal wallet list" } + eof { ::AlgorandGoal::Abort "EOF seeing expected input for spawn goal wallet list" } "*$WALLET_NAME*" {close} } } EXCEPTION ] } { @@ -375,18 +383,18 @@ proc ::AlgorandGoal::RecoverWallet { NEW_WALLET_NAME WALLET_PASSPHRASE NEW_WALLE set timeout 60 if { [catch { spawn goal wallet new -r $NEW_WALLET_NAME -d $TEST_PRIMARY_NODE_DIR - expect { - timeout { puts "TIMEOUT" } - {Please type your recovery mnemonic below, and hit return when you are done:*} { send "$WALLET_PASSPHRASE\r" } - } - for { set index 1} {$index <= 5} {incr index} { - expect { - timeout { puts "TIMEOUT" } - {Please choose a password for wallet* } { send "$NEW_WALLET_PASSWORD\r"} - {Please confirm the password:*} { send "$NEW_WALLET_PASSWORD\r"} - {Creating wallet...*} {puts $expect_out(buffer) } - -re {Created wallet '([-a-zA-Z0-9_]+)'} {set RECOVERED_WALLET_NAME $expect_out(1,string) } - } + expect { + timeout { ::AlgorandGoal::Abort "TIMEOUT" } + eof { ::AlgorandGoal::Abort "EOF" } + {Please type your recovery mnemonic below, and hit return when you are done:*} { send "$WALLET_PASSPHRASE\r" } + } + expect { + timeout { ::AlgorandGoal::Abort "TIMEOUT" } + eof { ::AlgorandGoal::Abort "EOF" } + {Please choose a password for wallet* } { send "$NEW_WALLET_PASSWORD\r"; exp_continue;} + {Please confirm the password:*} { send "$NEW_WALLET_PASSWORD\r"; exp_continue;} + {Creating wallet...*} {puts $expect_out(buffer); exp_continue; } + -re {Created wallet '([-a-zA-Z0-9_]+)'} {set RECOVERED_WALLET_NAME $expect_out(1,string) } } puts "Recovered wallet: $RECOVERED_WALLET_NAME" } EXCEPTION ] } { @@ -403,6 +411,7 @@ proc ::AlgorandGoal::CreateAccountForWallet { WALLET_NAME WALLET_PASSWORD TEST_P while 1 { expect { timeout { break; ::AlgorandGoal::Abort "Timed out seeing new account created for wallet $WALLET_NAME" } + eof { break; ::AlgorandGoal::Abort "EOF seeing new account created for wallet $WALLET_NAME" } "Please enter the password for wallet*" { send "$WALLET_PASSWORD\r" } -re {Created new account with address ([a-zA-Z0-9]+)} {set ACCOUNT_ADDRESS $expect_out(1,string) ;close; break } } @@ -422,6 +431,7 @@ proc ::AlgorandGoal::VerifyAccount { WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS while 1 { expect { timeout {break; ::AlgorandGoal::Abort "Timed out seeing expected account: $ACCOUNT_ADDRESS"} + eof {break; ::AlgorandGoal::Abort "EOF seeing expected account: $ACCOUNT_ADDRESS"} "Please enter the password for wallet*" { send "$WALLET_PASSWORD\r" } -re {\t([A-Z0-9]+)\t([A-Z0-9]+)} {set RETURN_ACCOUNT_ADDRESS $expect_out(1,string); break } } @@ -487,6 +497,7 @@ proc ::AlgorandGoal::GetAccountRewards { WALLET_NAME ACCOUNT_ADDRESS TEST_PRIMAR spawn goal account rewards -w $WALLET_NAME -a $ACCOUNT_ADDRESS -d $TEST_PRIMARY_NODE_DIR expect { timeout { ::AlgorandGoal::Abort "Timed out retrieving account rewards for wallet $WALLET_NAME and account $ACCOUNT_ADDRESS" } + eof { ::AlgorandGoal::Abort "EOF retrieving account rewards for wallet $WALLET_NAME and account $ACCOUNT_ADDRESS" } -re {\d+} {set ACCOUNT_EARNINGS $expect_out(0,string)} } puts "Wallet: $WALLET_NAME, Account: $ACCOUNT_ADDRESS, Rewards: $ACCOUNT_EARNINGS" @@ -565,7 +576,8 @@ proc ::AlgorandGoal::AssetCreate { CREATOR WALLET_NAME WALLET_PASSWORD TOTAL_SUP expect { timeout { ::AlgorandGoal::Abort "Timed out create asset" } "Please enter the password for wallet '$WALLET_NAME':" { send "$WALLET_PASSWORD\r"; exp_continue } - eof + -re {Created asset with asset index (\d+)} { puts "Asset created"; close } + eof { ::AlgorandGoal::Abort "EOF create asset" } } } EXCEPTION ] } { ::AlgorandGoal::Abort "ERROR in AssetCreate: $EXCEPTION" @@ -652,9 +664,17 @@ proc ::AlgorandGoal::SplitGroup { INPUT_GROUP OUTPUT_GROUP } { if { [ catch { spawn goal clerk split -i $INPUT_GROUP -o $OUTPUT_GROUP expect { + #nolint:eof just checking status timeout { ::AlgorandGoal::Abort "Timed out splitting group transaction" } eof } + lassign [wait] PID SPAWNID OS_CODE ERR_CODE + if {$OS_CODE == -1} { + ::AlgorandGoal::Abort "Split group failed: OS error code: $ERR_CODE" + } + if {$ERR_CODE != 0} { + ::AlgorandGoal::Abort "Split group failed with: exit code: $ERR_CODE" + } } EXCEPTION ] } { ::AlgorandGoal::Abort "ERROR in Split Group: $EXCEPTION" } @@ -667,6 +687,7 @@ proc ::AlgorandGoal::LimitOrder {TEAL_DRIVER SWAP_N SWAP_D MIN_TRD OWNER FEE TIM spawn python $TEAL_DRIVER "limit-order" --swapn $SWAP_N --swapd $SWAP_D --mintrd $MIN_TRD --own $OWNER --fee $FEE --timeout $TIME_OUT --asset $ASSET_ID expect { timeout { ::AlgorandGoal::Abort "Timed out limit order" } + eof { ::AlgorandGoal::Abort "EOF limit order" } -re {^.+$} { puts $limitf $expect_out(buffer); close $limitf; close } } } EXCEPTION ] } { @@ -724,9 +745,16 @@ proc ::AlgorandGoal::SignTransaction { WALLET_NAME WALLET_PASSWORD INPUT_TXN OUT if { [ catch { spawn goal clerk sign -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME -i $INPUT_TXN -o $OUTPUT_TXN expect { + #nolint:eof just signing with outputting to file and checking status timeout { ::AlgorandGoal::Abort "Timed out signing transaction" } - "Please enter the password for wallet '$WALLET_NAME':" { send "$WALLET_PASSWORD\r" ; exp_continue} - eof + "Please enter the password for wallet '$WALLET_NAME':" { send "$WALLET_PASSWORD\r" ; exp_continue;} + } + lassign [wait] PID SPAWNID OS_CODE ERR_CODE + if {$OS_CODE == -1} { + ::AlgorandGoal::Abort "SignTransaction failed: OS error code: $ERR_CODE" + } + if {$ERR_CODE != 0} { + ::AlgorandGoal::Abort "SignTransaction failed with: exit code: $ERR_CODE" } } EXCEPTION ] } { ::AlgorandGoal::Abort "ERROR in SignTransaction: $EXCEPTION" @@ -741,6 +769,7 @@ proc ::AlgorandGoal::RawSend { TXN_FILE TEST_PRIMARY_NODE_DIR } { spawn goal clerk rawsend -f $TXN_FILE -d $TEST_PRIMARY_NODE_DIR expect { timeout { close; ::AlgorandGoal::Abort "Timed out rawsend $TXN_FILE" } + eof { close; ::AlgorandGoal::Abort "EOF rawsend $TXN_FILE" } -re {Transaction ([A-Z0-9]{52}) committed} {set TRANSACTION_ID $expect_out(1,string); close } -re {Rejected transactions written to (.+rej)} {::AlgorandGoal::Abort "RawSend rejected."} } @@ -810,6 +839,9 @@ proc ::AlgorandGoal::CheckNetworkAddressForCors { NET_ADDRESS } { expect { timeout { close; ::AlgorandGoal::Abort "Timeout failure in CheckNetworkAddressForCors" } "Access-Control-Allow-Origin" { puts "success" ; close } + eof { + return -code error "EOF without Access-Control-Allow-Origin in output" + } close } } EXCEPTION ] } { @@ -823,6 +855,7 @@ proc ::AlgorandGoal::GetLedgerSupply { TEST_PRIMARY_NODE_DIR } { spawn goal ledger supply -d $TEST_PRIMARY_NODE_DIR expect { timeout { ::AlgorandGoal::Abort "Get Ledger Supply timed out" } + eof { ::AlgorandGoal::Abort "Get Ledger Supply EOF" } -re {Round: (\d+)} {set ROUND $expect_out(1,string); exp_continue } -re {Total Money: (\d+)} {set TOTAL_MONEY $expect_out(1,string); exp_continue } -re {Online Money: (\d+)} {set ONLINE_MONEY $expect_out(1,string) } @@ -841,6 +874,7 @@ proc ::AlgorandGoal::CreateOneOfTwoMultisigForWallet { ADDRESS_1 ADDRESS_2 WALLE spawn goal account multisig new $ADDRESS_1 $ADDRESS_2 -T 1 -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME expect { timeout { ::AlgorandGoal::Abort "Timed out creating a multisig account from $ADDRESS_1 and $ADDRESS_2" } + eof { ::AlgorandGoal::Abort "EOF creating a multisig account from $ADDRESS_1 and $ADDRESS_2" } "Please enter the password for wallet*" { send "$WALLET_PASSWORD\r" } -re {Created new account with address ([a-zA-Z0-9]+)} { set MULTISIG_ADDRESS $expect_out(1,string); @@ -859,6 +893,7 @@ proc ::AlgorandGoal::VerifyMultisigInfoForOneOfTwoMultisig { MULTISIG_ADDRESS AD spawn goal account multisig info --address $MULTISIG_ADDRESS -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME expect { timeout { ::AlgorandGoal::Abort "Timed out querying info about multisig account $MULTISIG_ADDRESS" } + eof { ::AlgorandGoal::Abort "EOF querying info about multisig account $MULTISIG_ADDRESS" } -re {Version: (\d+)\s+Threshold: (\d+)\s+Public keys:\s+([a-zA-Z0-9]+)\s+([a-zA-Z0-9]+)\s+} { set VERSION $expect_out(1,string); set THRESHOLD $expect_out(2,string); @@ -879,7 +914,14 @@ proc ::AlgorandGoal::VerifyMultisigInfoForOneOfTwoMultisig { MULTISIG_ADDRESS AD proc ::AlgorandGoal::DeleteMultisigAccount { MULTISIG_ADDRESS TEST_PRIMARY_NODE_DIR } { if { [ catch { spawn goal account multisig delete --address $MULTISIG_ADDRESS -d $TEST_PRIMARY_NODE_DIR - expect {*} + expect eof + lassign [wait] PID SPAWNID OS_CODE ERR_CODE + if {$OS_CODE == -1} { + ::AlgorandGoal::Abort "DeleteMultisigAccount failed: OS error code: $ERR_CODE" + } + if {$ERR_CODE != 0} { + ::AlgorandGoal::Abort "DeleteMultisigAccount failed with: exit code: $ERR_CODE" + } } EXCEPTION ] } { ::AlgorandGoal::Abort "ERROR in DeleteMultisigAccount: $EXCEPTION" } @@ -1046,6 +1088,7 @@ proc ::AlgorandGoal::Report { TEST_PRIMARY_NODE_DIR } { spawn goal report -d $TEST_PRIMARY_NODE_DIR expect { timeout { ::AlgorandGoal::Abort "goal report timed out" } + eof { ::AlgorandGoal::Abort "goal report EOF" } "source code available at https://github.com/algorand/go-algorand" {puts "goal -v ok"} -re {Genesis ID from genesis.json: *} {puts "genesis ID from genesis.json ok"} -re {Last committed block: (\d+)} {puts "status check ok"} @@ -1318,8 +1361,13 @@ proc ::AlgorandGoal::CheckEOF { { ERROR_STRING "" } } { proc ::AlgorandGoal::InspectTransactionFile { TRX_FILE } { puts "\n Inspect $TRX_FILE" spawn goal clerk inspect $TRX_FILE - expect { - eof + expect eof + lassign [wait] PID SPAWNID OS_CODE ERR_CODE + if {$OS_CODE == -1} { + ::AlgorandGoal::Abort "InspectTransactionFile failed: OS error code: $ERR_CODE" + } + if {$ERR_CODE != 0} { + ::AlgorandGoal::Abort "InspectTransactionFile failed with: exit code: $ERR_CODE" } } @@ -1348,4 +1396,3 @@ proc ::AlgorandGoal::RunPingpong {DURATION PINGPONG_OPTIONS TEST_PRIMARY_NODE_DI ::AlgorandGoal::Abort "ERROR in RunPingpong: $EXCEPTION" } } - diff --git a/test/e2e-go/cli/goal/expect/goalTxValidityTest.exp b/test/e2e-go/cli/goal/expect/goalTxValidityTest.exp index 37acb80ce3..8909693fed 100644 --- a/test/e2e-go/cli/goal/expect/goalTxValidityTest.exp +++ b/test/e2e-go/cli/goal/expect/goalTxValidityTest.exp @@ -32,6 +32,7 @@ proc TestLastValidInTx { CMD TX_FILE EXPECTED_LAST_VALID } { spawn goal clerk inspect $TX_FILE expect { timeout { ::AlgorandGoal::Abort "'goal clerk inspect' timed out" } + eof { ::AlgorandGoal::Abort "'goal clerk inspect' eof" } -re {"lv": (\d+)} {set PASSED 1; set LAST_VALID $expect_out(1,string); close } } diff --git a/test/e2e-go/cli/goal/expect/limitOrderTest.exp b/test/e2e-go/cli/goal/expect/limitOrderTest.exp index 26d899f69d..a69a4275d6 100644 --- a/test/e2e-go/cli/goal/expect/limitOrderTest.exp +++ b/test/e2e-go/cli/goal/expect/limitOrderTest.exp @@ -91,10 +91,7 @@ if { [catch { set UNIT_NAME "duckcoin" ::AlgorandGoal::AssetCreate $ACCOUNT_1_ADDRESS $WALLET_1_NAME $WALLET_1_PASSWORD $TOTAL_SUPPLY 0 "" $UNIT_NAME $TEST_PRIMARY_NODE_DIR - # wait about 4 rounds - set ASSET_CREATE_WAIT 20 - puts "Wait $ASSET_CREATE_WAIT for asset creation" - exec sleep $ASSET_CREATE_WAIT + # no extra waiting here since AssetCreate waits for confirmation # get asset id set ASSET_ID [::AlgorandGoal::AssetLookup $ACCOUNT_1_ADDRESS $UNIT_NAME $TEST_PRIMARY_NODE_DIR] @@ -124,7 +121,8 @@ if { [catch { puts "Generated Teal Source:" spawn cat $TEAL_SOURCE expect { - -re {^.+$} { close } + #nolint:eof not asserting expected output + -re {^.+$} { close } } # compile teal assembly to bytecode @@ -146,10 +144,10 @@ if { [catch { # the second payment sends money (the asset) from the Bob to the Alice set ZERO_ADDRESS "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ" set LIMIT_TXN_1 "$TEST_ROOT_DIR/limit1.tx" - ::AlgorandGoal::TealTxnCreate $TEAL_SOURCE $ACCOUNT_1_ADDRESS $ZERO_ADDRESS 20000 $TEST_PRIMARY_NODE_DIR $LIMIT_TXN_1 + ::AlgorandGoal::TealTxnCreate $TEAL_SOURCE $ACCOUNT_1_ADDRESS $ZERO_ADDRESS 20000 $TEST_PRIMARY_NODE_DIR $LIMIT_TXN_1 set LIMIT_TXN_2 "$TEST_ROOT_DIR/limit2.tx" - ::AlgorandGoal::CreateAssetTransfer $ACCOUNT_1_ADDRESS $ACCOUNT_2_ADDRESS $ASSET_ID 30000 $TEST_PRIMARY_NODE_DIR $LIMIT_TXN_2 + ::AlgorandGoal::CreateAssetTransfer $ACCOUNT_1_ADDRESS $ACCOUNT_2_ADDRESS $ASSET_ID 30000 $TEST_PRIMARY_NODE_DIR $LIMIT_TXN_2 set LIMIT_CMB "$TEST_ROOT_DIR/limitcmb.tx" exec cat $LIMIT_TXN_1 $LIMIT_TXN_2 > $LIMIT_CMB diff --git a/test/e2e-go/cli/goal/expect/tealAndStatefulTealTest.exp b/test/e2e-go/cli/goal/expect/tealAndStatefulTealTest.exp index a32e2b4011..b4c6e817e1 100644 --- a/test/e2e-go/cli/goal/expect/tealAndStatefulTealTest.exp +++ b/test/e2e-go/cli/goal/expect/tealAndStatefulTealTest.exp @@ -141,9 +141,7 @@ if { [catch { ::AlgorandGoal::SplitGroup groupedtransactions.tx split.tx puts "sign the split transaction" - set RAW_TX_1 split-0.tx - set RAW_STX_1 signout-0.tx - ::AlgorandGoal::SignTransaction $WALLET_1_NAME $WALLET_1_PASSWORD $RAW_TX_1 $RAW_STX_1 $TEST_PRIMARY_NODE_DIR + ::AlgorandGoal::SignTransaction $WALLET_1_NAME $WALLET_1_PASSWORD split-0.tx signout-0.tx $TEST_PRIMARY_NODE_DIR puts "\ncombine into the sign out transaction" exec cat signout-0.tx split-1.tx > signout.tx diff --git a/test/e2e-go/cli/goal/expect/tealConsensusTest.exp b/test/e2e-go/cli/goal/expect/tealConsensusTest.exp index cd2b81cfd6..a4231acd96 100644 --- a/test/e2e-go/cli/goal/expect/tealConsensusTest.exp +++ b/test/e2e-go/cli/goal/expect/tealConsensusTest.exp @@ -50,6 +50,7 @@ if { [catch { spawn goal clerk compile "$TEST_ROOT_DIR/small-sig.teal" expect { -re {[A-Z2-9]{58}} { set SMALL_SIG $expect_out(0,string) } + eof { ::AlgorandGoal::Abort $expect_out(buffer) } "\n" { ::AlgorandGoal::Abort $expect_out(buffer) } } @@ -58,6 +59,7 @@ if { [catch { expect { -re {[A-Z2-9]{58}} { ::AlgorandGoal::Abort "hash" } -re {.*logicsig program size too large} { puts "bigsigcheck: pass" } + eof { ::AlgorandGoal::Abort $expect_out(buffer) } "\n" { ::AlgorandGoal::Abort $expect_out(buffer) } } @@ -65,6 +67,7 @@ if { [catch { spawn goal clerk compile "$TEST_ROOT_DIR/barely-fits-app.teal" expect { -re {[A-Z2-9]{58}} { puts "hash $expect_out(0,string)" } + eof { ::AlgorandGoal::Abort $expect_out(buffer) } "\n" { ::AlgorandGoal::Abort $expect_out(buffer) } } @@ -74,6 +77,7 @@ if { [catch { expect { -re {[A-Z2-9]{58}} { ::AlgorandGoal::Abort "hash" } -re {.*app program size too large} { puts "bigappcheck: pass" } + eof { ::AlgorandGoal::Abort $expect_out(buffer) } "\n" { ::AlgorandGoal::Abort $expect_out(buffer) } } @@ -84,9 +88,10 @@ if { [catch { " - pass -" { puts "small-sig dryrun pass" } "REJECT" { ::AlgorandGoal::Abort $expect_out(buffer) } "static cost budget" { ::AlgorandGoal::Abort $expect_out(buffer) } + eof { ::AlgorandGoal::Abort $expect_out(buffer) } } - teal "$TEST_ROOT_DIR/slow-sig.teal" 2 1 20001 + teal "$TEST_ROOT_DIR/slow-sig.teal" 4 1 20001 exec goal clerk compile "$TEST_ROOT_DIR/slow-sig.teal" exec goal clerk send -F "$TEST_ROOT_DIR/slow-sig.teal" -t GXBNLU4AXQABPLHXJDMTG2YXSDT4EWUZACT7KTPFXDQW52XPTIUS5OZ5HQ -a 100 -d $TEST_PRIMARY_NODE_DIR -o $TEST_ROOT_DIR/slow-sig.tx spawn goal clerk dryrun -P future -t $TEST_ROOT_DIR/slow-sig.tx # Should succeed Check, fail Eval @@ -94,6 +99,7 @@ if { [catch { "dynamic cost budget" { puts "slow-sig dryrun pass" } " - pass -" { ::AlgorandGoal::Abort $expect_out(buffer) } "REJECT" { ::AlgorandGoal::Abort $expect_out(buffer) } + eof { ::AlgorandGoal::Abort $expect_out(buffer) } } # Shutdown the network diff --git a/test/e2e-go/cli/goal/expect/testInfraTest.exp b/test/e2e-go/cli/goal/expect/testInfraTest.exp index b5785e2807..e4172a89f6 100644 --- a/test/e2e-go/cli/goal/expect/testInfraTest.exp +++ b/test/e2e-go/cli/goal/expect/testInfraTest.exp @@ -58,6 +58,7 @@ proc checkProcessReturnedCodeTest {} { 44 { close } + eof { puts "expected output not 44"; exit 1 } } lassign [::AlgorandGoal::CheckProcessReturnedCode 0] response OS_CODE ERR_CODE KILLED KILL_SIGNAL EXP if {$response != 0} { @@ -72,6 +73,7 @@ proc checkProcessReturnedCodeTest {} { 44 { puts "not closing" } + eof { puts "expected output not 44"; exit 1 } } lassign [::AlgorandGoal::CheckProcessReturnedCode 0] response OS_CODE ERR_CODE KILLED KILL_SIGNAL EXP if {$KILLED != "CHILDKILLED" || $KILL_SIGNAL != "SIGSEGV" || $EXP != "segmentation violation"} { From 37a9a18838a6521f86ec3578d52a1e694f846f3c Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 9 Sep 2024 12:05:30 -0400 Subject: [PATCH 216/218] AVM: Derive looser, but more principled, checks of txn max size (#6114) Co-authored-by: Jason Paulos --- node/node_test.go | 43 +++++++++++++++++++++++++++++++++++++++++-- protocol/tags.go | 21 +++++++++++++++++---- protocol/tags_test.go | 4 ++++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index 6b991751cb..19463177df 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -33,9 +33,11 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" + csp "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" @@ -805,11 +807,48 @@ func TestMaxSizesCorrect(t *testing.T) { require.Equal(t, ppSize, protocol.ProposalPayloadTag.MaxMessageSize()) spSize := uint64(stateproof.SigFromAddrMaxSize()) require.Equal(t, spSize, protocol.StateProofSigTag.MaxMessageSize()) - txSize := uint64(transactions.SignedTxnMaxSize()) - require.Equal(t, txSize, protocol.TxnTag.MaxMessageSize()) msSize := uint64(crypto.DigestMaxSize()) require.Equal(t, msSize, protocol.MsgDigestSkipTag.MaxMessageSize()) + // We want to check that the TxnTag's max size is big enough, but it is + // foolish to try to be exact here. We will confirm that it is bigger that + // a stateproof txn (the biggest kind, which can only appear by itself), and + // that it is bigger than 16 times the largest transaction other than + // stateproof txn. + txTagMax := protocol.TxnTag.MaxMessageSize() + + // SignedTxnMaxSize() is an overestimate of a single transaction because it + // includes fields from all the different types of signatures, and types of + // transactions. First, we remove the aspects of the overestimate that come + // from the multiple signature types. + maxCombinedTxnSize := uint64(transactions.SignedTxnMaxSize()) + // subtract out the two smaller signature sizes (logicsig is biggest, it can *contain* the others) + maxCombinedTxnSize -= uint64(crypto.SignatureMaxSize() + crypto.MultisigSigMaxSize()) + // the logicsig size is *also* an overestimate, because it thinks each + // logicsig arg can be big, but really the sum of the args and the program + // has a max size. + maxCombinedTxnSize -= uint64(transactions.EvalMaxArgs * config.MaxLogicSigMaxSize) + + // maxCombinedTxnSize is still an overestimate because it assumes all txn + // type fields can be in the same txn. That's not true, but it provides an + // upper bound on the size of ONE transaction, even if the txn is a + // stateproof, which is big. Ensure our constant is big enough to hold one. + require.Greater(t, txTagMax, maxCombinedTxnSize) + + // we actually have to hold 16 txns, but in the case of multiple txns in a + // group, none can be stateproofs. So derive maxMinusSP, which is a per txn + // size estimate that excludes stateproof fields. + spTxnSize := uint64(csp.StateProofMaxSize() + stateproofmsg.MessageMaxSize()) + maxMinusSP := maxCombinedTxnSize - spTxnSize + require.Greater(t, txTagMax, 16*maxMinusSP) + // when we do logisig pooling, 16*maxMinusSP may be a large overshoot, since + // it will assume we can have a big logicsig in _each_ of the 16. It + // probably won't matter, since stateproof will still swamp it. But if so, + // remove 15 * MaxLogicSigMaxSize. + + // but we're not crazy. whichever of those is bigger - we don't need to be twice as big as that + require.Less(t, txTagMax, 2*max(maxCombinedTxnSize, 16*maxMinusSP)) + // UE is a handrolled message not using msgp // including here for completeness ensured by protocol.TestMaxSizesTested ueSize := uint64(67) diff --git a/protocol/tags.go b/protocol/tags.go index 6cfcacd714..cdae9c6cdc 100644 --- a/protocol/tags.go +++ b/protocol/tags.go @@ -84,10 +84,23 @@ const StateProofSigTagMaxSize = 6378 // Matches current network.MaxMessageLength const TopicMsgRespTagMaxSize = 6 * 1024 * 1024 -// TxnTagMaxSize is the maximum size of a TxnTag message. This is equal to SignedTxnMaxSize() -// which is size of just a single message containing maximum Stateproof. Since Stateproof -// transactions can't be batched we don't need to multiply by MaxTxnBatchSize. -const TxnTagMaxSize = 4620031 +// TxnTagMaxSize is the maximum size of a TxnTag message. The TxnTag is used to +// send entire transaction groups. So, naively, we might set it to the maximum +// group size times the maximum transaction size (plus a little bit for msgpack +// encoding). But there are several reasons not to do that. First, the +// function we have for estimating max transaction size +// (transactions.SignedTxnMaxSize())) wildly overestimates the maximum +// transaction size because it is generated code that assumes _every_ +// transaction field can be set, but each transaction type has mutually +// exclusive fields. Second, the stateproof transaction is the biggest +// transaction by far, but it can only appear as a singleton, so it would not +// make sense to multiply it by 16. Finally, we're going to pool logicsig code +// size, so while it's true that one transaction in a group could have a 16k +// logicsig, that would only be true if the other transactions had 0 bytes of +// logicsig. So we will use a bound that is a bit bigger that a txn group can +// be, but avoid trying to be precise. See TestMaxSizesCorrect for the detailed +// reasoning. +const TxnTagMaxSize = 5_000_000 // UniEnsBlockReqTagMaxSize is the maximum size of a UniEnsBlockReqTag message const UniEnsBlockReqTagMaxSize = 67 diff --git a/protocol/tags_test.go b/protocol/tags_test.go index 137bf4e3f7..69c3146cf9 100644 --- a/protocol/tags_test.go +++ b/protocol/tags_test.go @@ -169,6 +169,10 @@ func TestMaxSizesTested(t *testing.T) { } for _, tag := range constTags { + if tag == "TxnTag" { + // TxnTag is tested in a looser way in TestMaxSizesCorrect + continue + } require.Truef(t, tagsFound[tag], "Tag %s does not have a corresponding test in TestMaxSizesCorrect", tag) } } From 1d08955152091af7fe9e02250b0bc90b929dcda2 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:07:12 -0400 Subject: [PATCH 217/218] p2p: make sure p2p http server runs on all interfaces (#6123) --- network/p2p/http.go | 10 ++++++++++ network/p2p/p2p.go | 3 ++- network/p2pNetwork.go | 1 - network/p2pNetwork_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/network/p2p/http.go b/network/p2p/http.go index 633a13713d..f11b5375ab 100644 --- a/network/p2p/http.go +++ b/network/p2p/http.go @@ -28,6 +28,7 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" + "github.com/multiformats/go-multiaddr" ) // algorandP2pHTTPProtocol defines a libp2p protocol name for algorand's http over p2p messages @@ -46,6 +47,15 @@ func MakeHTTPServer(streamHost host.Host) *HTTPServer { Host: libp2phttp.Host{StreamHost: streamHost}, p2phttpMux: mux.NewRouter(), } + // libp2phttp server requires either explicit ListenAddrs or streamHost.Addrs() to be non-empty. + // If streamHost.Addrs() is empty, we will listen on all interfaces + if len(streamHost.Addrs()) == 0 { + logging.Base().Debugf("MakeHTTPServer: no addresses for %s, asking to listen all interfaces", streamHost.ID()) + httpServer.ListenAddrs = []multiaddr.Multiaddr{ + multiaddr.StringCast("/ip4/0.0.0.0/tcp/0/http"), + } + httpServer.InsecureAllowHTTP = true + } return &httpServer } diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 4ddda54157..f281d1d13b 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -39,6 +39,7 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" + basichost "github.com/libp2p/go-libp2p/p2p/host/basic" rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" "github.com/libp2p/go-libp2p/p2p/security/noise" @@ -277,7 +278,7 @@ func (s *serviceImpl) dialNode(ctx context.Context, peer *peer.AddrInfo) error { func (s *serviceImpl) AddrInfo() peer.AddrInfo { return peer.AddrInfo{ ID: s.host.ID(), - Addrs: s.host.Addrs(), + Addrs: s.host.(*basichost.BasicHost).AllAddrs(), // fetch all addresses, including private ones } } diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index f88660b653..aa991d0429 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -513,7 +513,6 @@ func (n *P2PNetwork) Address() (string, bool) { for _, addr := range addrs { if !manet.IsIPLoopback(addr) && !manet.IsIPUnspecified(addr) { return addr.String(), true - } } // We don't have a non loopback address, so just return the first one if it contains an ip4 address or port diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index dcc641c350..d18196c4c4 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -789,6 +789,44 @@ func TestP2PHTTPHandler(t *testing.T) { require.ErrorIs(t, err, limitcaller.ErrConnectionQueueingTimeout) } +// TestP2PHTTPHandlerAllInterfaces makes sure HTTP server runs even if NetAddress is set to a non-routable address +func TestP2PHTTPHandlerAllInterfaces(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.EnableDHTProviders = false + cfg.GossipFanout = 1 + cfg.NetAddress = ":0" + log := logging.TestingLog(t) + + netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil) + require.NoError(t, err) + + h := &p2phttpHandler{t, "hello", nil} + netA.RegisterHTTPHandler("/test", h) + + netA.Start() + defer netA.Stop() + + peerInfoA := netA.service.AddrInfo() + addrsB, err := peer.AddrInfoToP2pAddrs(&peerInfoA) + require.NoError(t, err) + require.NotZero(t, addrsB[0]) + + t.Logf("peerInfoB: %s", peerInfoA) + httpClient, err := p2p.MakeHTTPClient(&peerInfoA) + require.NoError(t, err) + resp, err := httpClient.Get("/test") + require.NoError(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, "hello", string(body)) + +} + // TestP2PRelay checks p2p nodes can properly relay messages: // netA and netB are started with ForceFetchTransactions so it subscribes to the txn topic, // both of them are connected and do not relay messages. From 3b3b5ce00dd3434cb2c692d102ff43eb11be8155 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:48:34 -0400 Subject: [PATCH 218/218] scripts: allow metrics filtering by labels in metrics_viz.py (#6125) --- test/heapwatch/heapWatch.py | 56 ++++++++++++++++++++++++++++++---- test/heapwatch/metrics_aggs.py | 13 ++------ test/heapwatch/metrics_lib.py | 30 +++++++++++++++--- test/heapwatch/metrics_viz.py | 13 ++++++-- 4 files changed, 88 insertions(+), 24 deletions(-) diff --git a/test/heapwatch/heapWatch.py b/test/heapwatch/heapWatch.py index e62cc7ab80..f242ecd06b 100644 --- a/test/heapwatch/heapWatch.py +++ b/test/heapwatch/heapWatch.py @@ -122,6 +122,9 @@ def __init__(self, path, net=None, token=None, admin_token=None): self._algod = None self.timeout = 15 + def __repr__(self): + return ''.format(self.path) + def pid(self): if self._pid is None: if not self.isdir: @@ -159,6 +162,32 @@ def get_pprof_snapshot(self, name, snapshot_name=None, outdir=None, timeout=None logger.debug('%s -> %s', self.nick, outpath) return outpath + def get_debug_settings_pprof(self): + timeout = self.timeout + url = 'http://' + self.net + '/debug/settings/pprof' + headers = self.headers.copy() + headers['X-Algo-API-Token'] = self.admin_token + try: + response = urllib.request.urlopen(urllib.request.Request(url, headers=headers), timeout=timeout) + except Exception as e: + logger.error('could not fetch %s from %s via %r (%s)', '/debug/settings/pprof', self.path, url, e) + return + blob = response.read() + return json.loads(blob) + + def set_debug_settings_pprof(self, settings): + timeout = self.timeout + url = 'http://' + self.net + '/debug/settings/pprof' + headers = self.headers.copy() + headers['X-Algo-API-Token'] = self.admin_token + data = json.dumps(settings).encode() + try: + response = urllib.request.urlopen(urllib.request.Request(url, data=data, headers=headers, method='PUT'), timeout=timeout) + except Exception as e: + logger.error('could not put %s to %s via %r (%s)', settings, self.path, url, e) + return + response.close() + def get_heap_snapshot(self, snapshot_name=None, outdir=None): return self.get_pprof_snapshot('heap', snapshot_name, outdir) @@ -355,6 +384,27 @@ def do_snap(self, now, get_cpu=False, fraction=False): rss, vsz = rssvsz with open(os.path.join(self.args.out, nick + '.heap.csv'), 'at') as fout: fout.write('{},{},{},{}\n'.format(snapshot_name,snapshot_isotime,rss, vsz)) + if self.args.mutex or self.args.block: + # get mutex/blocking profiles state and enable as needed + for ad in self.they: + settings = ad.get_debug_settings_pprof() + if not settings: + # failed to get settings, probably disabled + continue + updated = False + if self.args.mutex: + mrate = settings.get('mutex-rate', 0) + if mrate == 0: + settings['mutex-rate'] = 5 # 1/5 of events recorded + updated = True + if self.args.block: + brate = settings.get('block-rate', 0) + if brate == 0: + settings['block-rate'] = 100 # one blocking event per 100 nanoseconds spent blocked. + updated = True + if updated: + logger.debug('enabling mutex/blocking profiles on %s', ad.path) + ad.set_debug_settings_pprof(settings) if self.args.goroutine: for ad in self.they: ad.get_goroutine_snapshot(snapshot_name, outdir=self.args.out) @@ -466,12 +516,6 @@ def main(): else: logging.basicConfig(level=logging.INFO) - if args.block: - print('Ensure algod is compiled with `runtime.SetBlockProfileRate()` set') - - if args.mutex: - print('Ensure algod is compiled with `runtime.SetMutexProfileFraction()` set') - for nre in args.tf_name_re: try: # do re.compile just to check diff --git a/test/heapwatch/metrics_aggs.py b/test/heapwatch/metrics_aggs.py index d20593c097..33379766a6 100644 --- a/test/heapwatch/metrics_aggs.py +++ b/test/heapwatch/metrics_aggs.py @@ -33,7 +33,7 @@ from plotly.subplots import make_subplots -from metrics_lib import Metric, MetricType, parse_metrics, gather_metrics_files_by_nick +from metrics_lib import Metric, MetricType, parse_metrics, gather_metrics_files_by_nick, parse_tags logger = logging.getLogger(__name__) @@ -62,14 +62,7 @@ def main(): else: logging.basicConfig(level=logging.INFO) - tags = {} - if args.tags: - for tag in args.tags: - if '=' not in tag: - raise (f'Invalid tag: {tag}') - k, v = tag.split('=', 1) - tags[k] = v - tag_keys = set(tags.keys()) + tags, tag_keys = parse_tags(args.tags) metrics_files = sorted(glob.glob(os.path.join(args.dir, '*.metrics'))) metrics_files.extend(glob.glob(os.path.join(args.dir, 'terraform-inventory.host'))) @@ -119,7 +112,7 @@ def main(): for metric in metrics_seq: if metric.type != MetricType.COUNTER: raise RuntimeError('Only COUNT metrics are supported') - if tags is None or tags is not None and metric.has_tags(tag_keys, tags): + if tags is None or tags is not None and metric.has_tags(tags, tag_keys): raw_value += metric.value full_name = metric.string(set(tag_keys).union({'n'})) diff --git a/test/heapwatch/metrics_lib.py b/test/heapwatch/metrics_lib.py index 5fc7b36075..1ac0d85bd7 100644 --- a/test/heapwatch/metrics_lib.py +++ b/test/heapwatch/metrics_lib.py @@ -26,7 +26,7 @@ import os import re import sys -from typing import Dict, Iterable, List, Optional, Tuple, Union +from typing import Dict, Iterable, List, Optional, Set, Tuple, Union from urllib.parse import urlparse @@ -210,14 +210,15 @@ def add_tag(self, key: str, value: str): self.tags[key] = value self.tag_keys.add(key) - def has_tags(self, tag_keys: set, tags: Dict[str, str]): + def has_tags(self, tags: Dict[str, Tuple[str, ...]], tag_keys: Set[str] | None) -> bool: """return True if all tags are present in the metric tags tag_keys are not strictly needed but used as an optimization """ - if self.tag_keys.intersection(tag_keys) != tag_keys: + if tag_keys is not None and self.tag_keys.intersection(tag_keys) != tag_keys: return False - for k, v in tags.items(): - if self.tags.get(k) != v: + for k, vals in tags.items(): + v = self.tags.get(k) + if v not in vals: return False return True @@ -270,3 +271,22 @@ def parse_metrics( out = [{name: metric}] return out + +def parse_tags(tag_pairs: List[str]) -> Tuple[Dict[str, Tuple[str, ...]], Set[str]]: + tags = {} + keys = set() + if not tag_pairs: + return tags, keys + + for tag in tag_pairs: + if '=' not in tag: + raise ValueError(f'Invalid tag: {tag}') + k, v = tag.split('=', 1) + val = tags.get(k) + if val is None: + tags[k] = (v,) + else: + tags[k] = val + (v,) + keys.add(k) + + return tags, keys \ No newline at end of file diff --git a/test/heapwatch/metrics_viz.py b/test/heapwatch/metrics_viz.py index 741aa2dd73..840d109ce8 100644 --- a/test/heapwatch/metrics_viz.py +++ b/test/heapwatch/metrics_viz.py @@ -23,7 +23,7 @@ import plotly.graph_objs as go from plotly.subplots import make_subplots -from metrics_lib import MetricType, parse_metrics, gather_metrics_files_by_nick +from metrics_lib import MetricType, parse_metrics, gather_metrics_files_by_nick, parse_tags logger = logging.getLogger(__name__) @@ -42,6 +42,7 @@ def main(): ap.add_argument('--nick-lre', action='append', default=[], help='label:regexp to filter node names, may be repeated') ap.add_argument('-s', '--save', type=str, choices=['png', 'html'], help=f'save plot to \'{default_img_filename}\' or \'{default_html_filename}\' 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('-t', '--tags', action='append', default=[], help='tag/label pairs in a=b format to aggregate by, may be repeated. Empty means aggregation by metric name') ap.add_argument('--verbose', default=False, action='store_true') args = ap.parse_args() @@ -54,6 +55,8 @@ def main(): logging.error('need at least one dir set with -d/--dir') return 1 + tags, tag_keys = parse_tags(args.tags) + metrics_files = sorted(glob.glob(os.path.join(args.dir, '*.metrics'))) metrics_files.extend(glob.glob(os.path.join(args.dir, 'terraform-inventory.host'))) filesByNick = gather_metrics_files_by_nick(metrics_files, args.nick_re, args.nick_lre) @@ -100,6 +103,9 @@ def main(): for metric in metrics_seq: raw_value = metric.value + if tags and not metric.has_tags(tags, tag_keys): + continue + full_name = metric.string() if full_name not in data: # handle gaps in data, sometimes metric file might miss a value @@ -122,8 +128,9 @@ def main(): active_metric_names.append(full_name) - active_metric_names.sort() - active_metrics[metric_name] = active_metric_names + if active_metric_names: + active_metric_names.sort() + active_metrics[metric_name] = active_metric_names idx += 1 for i, metric_pair in enumerate(sorted(active_metrics.items())):