diff --git a/.golangci.yml b/.golangci.yml index 551341b0f9..c6fdb978d6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,7 +24,8 @@ linters: - staticcheck - testifylint - unused - + - forbidigo + settings: dupword: comments-only: true @@ -101,6 +102,11 @@ linters: enable: - error-is-as disable-all: true + forbidigo: + forbid: + - p: ^(require|assert)\.Error$ + pkg: ^github\.com/stretchr/testify/(require|assert)$ + msg: use ErrorContains, ErrorIs, or ErrorAs instead exclusions: generated: lax @@ -147,6 +153,8 @@ linters: text: '^empty-block: this block is empty, you can remove it' - linters: revive text: '^superfluous-else: if block ends with .* so drop this else and outdent its block' + - linters: forbidigo + path-except: ^network/ issues: # Work our way back over time to be clean against all these @@ -164,7 +172,15 @@ severity: formatters: enable: + - gci - gofmt + settings: + gci: + sections: + - standard + - default + - prefix(github.com/algorand) + - prefix(github.com/algorand/go-algorand) exclusions: generated: lax paths: diff --git a/AGENTS.md b/AGENTS.md index 88625609f6..600e54d008 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -154,6 +154,11 @@ Ledger uses independent state machines that can rebuild from blockchain events, - Race detection enabled for concurrent code validation - Benchmark tests for performance-critical paths +### Paginated Resource Lookups (`ledger/acctupdates.go`) +The `lookupAssetResources`, `lookupApplicationResources`, and `lookupBoxResources` functions follow the same pattern: walk in-memory deltas backwards, query the DB, then merge the two result sets. These functions must stay closely aligned — a bug fix or structural change in one almost certainly requires the same change in the others. They are candidates for future consolidation into shared generic logic. + +Their corresponding `Ledger`-level wrappers in `ledger/ledger.go` (`LookupAssets`, `LookupApplications`, `LookupBoxes`) must each hold `trackerMu.RLock()`, matching every other method that accesses tracker state. + ### Code Organization - Interface-first design for testability and modularity - Dependency injection for component assembly diff --git a/Makefile b/Makefile index e9552dcafc..66aaa930e8 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ GOTAGSLIST := sqlite_unlock_notify sqlite_omit_load_extension GOTAGSLIST += ${GOTAGSCUSTOM} GOTESTCOMMAND := go tool -modfile=tool.mod gotestsum --format pkgname --jsonfile testresults.json -- +GOLINTCOMMAND := go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7.1 -c .golangci.yml ifeq ($(OS_TYPE), darwin) # M1 Mac--homebrew install location in /opt/homebrew @@ -97,6 +98,7 @@ default: build fmt: go fmt ./... + $(GOLINTCOMMAND) fmt ./scripts/check_license.sh -i fix: build @@ -106,7 +108,7 @@ modernize: GOTOOLCHAIN=auto go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@v0.39.0 -any=false -bloop=false -rangeint=false -fmtappendf=false -waitgroup=false -stringsbuilder=false -omitzero=false -fix ./... lint: - go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.7.1 run -c .golangci.yml + $(GOLINTCOMMAND) run warninglint: custom-golangci-lint ./custom-golangci-lint run -c .golangci-warnings.yml diff --git a/agreement/actions.go b/agreement/actions.go index 7f1ee27351..c6b3c76996 100644 --- a/agreement/actions.go +++ b/agreement/actions.go @@ -464,34 +464,28 @@ func disconnectAction(e messageEvent, err *serializableError) action { return networkAction{T: disconnect, Err: err, h: e.Input.messageHandle} } -func broadcastAction(tag protocol.Tag, o interface{}) action { - a := networkAction{T: broadcast, Tag: tag} - // TODO would be good to have compiler check this (and related) type switch - // by specializing one method per type - switch tag { - case protocol.AgreementVoteTag: - a.UnauthenticatedVote = o.(unauthenticatedVote) - case protocol.VoteBundleTag: - a.UnauthenticatedBundle = o.(unauthenticatedBundle) - case protocol.ProposalPayloadTag: - a.CompoundMessage = o.(compoundMessage) - } - return a +// note, there is no broadcastVoteAction +// because when a participant makes a vote, it is processed as voteVerified / handleMessageEvent events +// and then processed like any other votes, resulting in a relayVoteAction instead of a broadcast + +func broadcastBundleAction(b unauthenticatedBundle) action { + return networkAction{T: broadcast, Tag: protocol.VoteBundleTag, UnauthenticatedBundle: b} } -func relayAction(e messageEvent, tag protocol.Tag, o interface{}) action { - a := networkAction{T: relay, h: e.Input.messageHandle, Tag: tag} - // TODO would be good to have compiler check this (and related) type switch - // by specializing one method per type - switch tag { - case protocol.AgreementVoteTag: - a.UnauthenticatedVote = o.(unauthenticatedVote) - case protocol.VoteBundleTag: - a.UnauthenticatedBundle = o.(unauthenticatedBundle) - case protocol.ProposalPayloadTag: - a.CompoundMessage = o.(compoundMessage) - } - return a +func broadcastCompoundAction(msg compoundMessage) action { + return networkAction{T: broadcast, Tag: protocol.ProposalPayloadTag, CompoundMessage: msg} +} + +func relayVoteAction(e messageEvent, v unauthenticatedVote) action { + return networkAction{T: relay, h: e.Input.messageHandle, Tag: protocol.AgreementVoteTag, UnauthenticatedVote: v} +} + +func relayBundleAction(e messageEvent, b unauthenticatedBundle) action { + return networkAction{T: relay, h: e.Input.messageHandle, Tag: protocol.VoteBundleTag, UnauthenticatedBundle: b} +} + +func relayCompoundAction(e messageEvent, msg compoundMessage) action { + return networkAction{T: relay, h: e.Input.messageHandle, Tag: protocol.ProposalPayloadTag, CompoundMessage: msg} } func verifyVoteAction(e messageEvent, r round, p period, taskIndex uint64) action { diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go index 5c61a91af7..2c7d891cf2 100644 --- a/agreement/agreementtest/simulate_test.go +++ b/agreement/agreementtest/simulate_test.go @@ -26,9 +26,10 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" diff --git a/agreement/common_test.go b/agreement/common_test.go index 6081b2dde8..7d53727aa6 100644 --- a/agreement/common_test.go +++ b/agreement/common_test.go @@ -23,9 +23,10 @@ import ( "math/rand" "testing" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" diff --git a/agreement/credentialArrivalHistory_test.go b/agreement/credentialArrivalHistory_test.go index 915b7f1079..30a9bbb556 100644 --- a/agreement/credentialArrivalHistory_test.go +++ b/agreement/credentialArrivalHistory_test.go @@ -20,8 +20,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestCredentialHistoryStore(t *testing.T) { diff --git a/agreement/cryptoVerifier_test.go b/agreement/cryptoVerifier_test.go index 8880adcf55..1d6206cc84 100644 --- a/agreement/cryptoVerifier_test.go +++ b/agreement/cryptoVerifier_test.go @@ -24,10 +24,11 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" diff --git a/agreement/dynamicFilterTimeoutParams_test.go b/agreement/dynamicFilterTimeoutParams_test.go index 0a5af5e8dd..7b658aecda 100644 --- a/agreement/dynamicFilterTimeoutParams_test.go +++ b/agreement/dynamicFilterTimeoutParams_test.go @@ -20,8 +20,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestSampleIndexIsValid(t *testing.T) { diff --git a/agreement/events_test.go b/agreement/events_test.go index 8bdaf93004..d829f9c9fd 100644 --- a/agreement/events_test.go +++ b/agreement/events_test.go @@ -20,9 +20,10 @@ import ( "encoding/base64" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // TestSerializableErrorBackwardCompatibility ensures Err field of type serializableError can be diff --git a/agreement/fuzzer/dropMessageFilter_test.go b/agreement/fuzzer/dropMessageFilter_test.go index 832d54dc8e..71c77914b5 100644 --- a/agreement/fuzzer/dropMessageFilter_test.go +++ b/agreement/fuzzer/dropMessageFilter_test.go @@ -18,6 +18,7 @@ package fuzzer import ( "encoding/json" + "github.com/algorand/go-algorand/protocol" ) diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go index 45a3ae743e..476115f1cf 100644 --- a/agreement/fuzzer/ledger_test.go +++ b/agreement/fuzzer/ledger_test.go @@ -22,6 +22,8 @@ import ( "maps" "math/rand" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -30,7 +32,6 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-deadlock" ) const randseed = 0 diff --git a/agreement/fuzzer/messageDelayFilter_test.go b/agreement/fuzzer/messageDelayFilter_test.go index b1c24c1621..aefe7a1bf6 100644 --- a/agreement/fuzzer/messageDelayFilter_test.go +++ b/agreement/fuzzer/messageDelayFilter_test.go @@ -19,6 +19,7 @@ package fuzzer import ( "container/heap" "encoding/json" + "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/protocol" diff --git a/agreement/fuzzer/messageDuplicationFilter_test.go b/agreement/fuzzer/messageDuplicationFilter_test.go index 00f4a65a14..22760f0fb1 100644 --- a/agreement/fuzzer/messageDuplicationFilter_test.go +++ b/agreement/fuzzer/messageDuplicationFilter_test.go @@ -20,8 +20,9 @@ import ( "container/heap" "encoding/json" - "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/protocol" ) // Duplicate message with delay diff --git a/agreement/fuzzer/messagePriorityQueue_test.go b/agreement/fuzzer/messagePriorityQueue_test.go index a0f65026c1..823c1f8a42 100644 --- a/agreement/fuzzer/messagePriorityQueue_test.go +++ b/agreement/fuzzer/messagePriorityQueue_test.go @@ -18,6 +18,7 @@ package fuzzer import ( "container/heap" + "github.com/algorand/go-algorand/protocol" ) diff --git a/agreement/fuzzer/messageReflectionFilter_test.go b/agreement/fuzzer/messageReflectionFilter_test.go index bf7e0c5428..ad4b3d6139 100644 --- a/agreement/fuzzer/messageReflectionFilter_test.go +++ b/agreement/fuzzer/messageReflectionFilter_test.go @@ -20,8 +20,9 @@ import ( "container/heap" "encoding/json" - "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/protocol" ) // Simulate a 2 way mirror where messages are passed through and also reflected back to sender with a delay diff --git a/agreement/fuzzer/tests_test.go b/agreement/fuzzer/tests_test.go index 098dfa7df2..ac1d401e37 100644 --- a/agreement/fuzzer/tests_test.go +++ b/agreement/fuzzer/tests_test.go @@ -24,7 +24,6 @@ import ( "math" "math/rand" "os" - //ossignal "os/signal" "path/filepath" //"runtime/pprof" @@ -34,9 +33,9 @@ import ( "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/logging" "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/logging" //"github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) diff --git a/agreement/player.go b/agreement/player.go index d6019d95c6..d7440bb60b 100644 --- a/agreement/player.go +++ b/agreement/player.go @@ -520,7 +520,7 @@ func (p *player) partitionPolicy(r routerHandle) (actions []action) { // TODO do we want to authenticate our own bundles? b := bundleResponse.Event.Bundle r.t.logBundleBroadcast(*p, b) - a0 := broadcastAction(protocol.VoteBundleTag, b) + a0 := broadcastBundleAction(b) actions = append(actions, a0) } @@ -550,7 +550,7 @@ func (p *player) partitionPolicy(r routerHandle) (actions []action) { if resStaged.Committable { transmit := compoundMessage{Proposal: resStaged.Payload.u()} r.t.logProposalRepropagate(resStaged.Proposal, bundleRound, bundlePeriod) - a1 := broadcastAction(protocol.ProposalPayloadTag, transmit) + a1 := broadcastCompoundAction(transmit) actions = append(actions, a1) } else { // even if there is no staged value, there may be a pinned value @@ -558,7 +558,7 @@ func (p *player) partitionPolicy(r routerHandle) (actions []action) { if resPinned.PayloadOK { transmit := compoundMessage{Proposal: resPinned.Payload.u()} r.t.logProposalRepropagate(resPinned.Proposal, bundleRound, bundlePeriod) - a1 := broadcastAction(protocol.ProposalPayloadTag, transmit) + a1 := broadcastCompoundAction(transmit) actions = append(actions, a1) } } @@ -628,7 +628,7 @@ func (p *player) handleMessageEvent(r routerHandle, e messageEvent) (actions []a // Dynamic filter timeout feature enabled, and current message // updated the best credential arrival time v := e.Input.Vote - return append(actions, relayAction(e, protocol.AgreementVoteTag, v.u())) + return append(actions, relayVoteAction(e, v.u())) case NoLateCredentialTrackingImpact: // Dynamic filter timeout feature enabled, but current message // may not update the best credential arrival time, so we should @@ -649,14 +649,14 @@ func (p *player) handleMessageEvent(r routerHandle, e messageEvent) (actions []a return append(actions, verifyVoteAction(e, uv.R.Round, uv.R.Period, seq)) } v := e.Input.Vote - a := relayAction(e, protocol.AgreementVoteTag, v.u()) + a := relayVoteAction(e, v.u()) ep := ef.(proposalAcceptedEvent) if ep.PayloadOk { transmit := compoundMessage{ Proposal: ep.Payload.u(), Vote: v.u(), } - a = broadcastAction(protocol.ProposalPayloadTag, transmit) + a = broadcastCompoundAction(transmit) } return append(actions, a) } @@ -677,7 +677,7 @@ func (p *player) handleMessageEvent(r routerHandle, e messageEvent) (actions []a uv := ef.(payloadProcessedEvent).Vote.u() // relay proposal if it has been pipelined - ra := relayAction(e, protocol.ProposalPayloadTag, compoundMessage{Proposal: up, Vote: uv}) + ra := relayCompoundAction(e, compoundMessage{Proposal: up, Vote: uv}) if ep.Round == p.Round { vpa := verifyPayloadAction(e, ep.Round, ep.Period, ep.Pinned) @@ -698,7 +698,7 @@ func (p *player) handleMessageEvent(r routerHandle, e messageEvent) (actions []a } up := e.Input.UnauthenticatedProposal - a := relayAction(e, protocol.ProposalPayloadTag, compoundMessage{Proposal: up, Vote: uv}) + a := relayCompoundAction(e, compoundMessage{Proposal: up, Vote: uv}) actions = append(actions, a) } @@ -740,7 +740,7 @@ func (p *player) handleMessageEvent(r routerHandle, e messageEvent) (actions []a return append(actions, verifyVoteAction(e, uv.R.Round, uv.R.Period, 0)) } // else e.t() == voteVerified v := e.Input.Vote - actions = append(actions, relayAction(e, protocol.AgreementVoteTag, v.u())) + actions = append(actions, relayVoteAction(e, v.u())) a1 := p.handle(r, ef) return append(actions, a1...) @@ -758,7 +758,7 @@ func (p *player) handleMessageEvent(r routerHandle, e messageEvent) (actions []a ub := e.Input.UnauthenticatedBundle return append(actions, verifyBundleAction(e, ub.Round, ub.Period, ub.Step)) } - a0 := relayAction(e, protocol.VoteBundleTag, ef.(thresholdEvent).Bundle) + a0 := relayBundleAction(e, ef.(thresholdEvent).Bundle) a1 := p.handle(r, ef) return append(append(actions, a0), a1...) } diff --git a/agreement/proposalManager_test.go b/agreement/proposalManager_test.go index 5a5c1d0387..561d0978cc 100644 --- a/agreement/proposalManager_test.go +++ b/agreement/proposalManager_test.go @@ -19,9 +19,10 @@ package agreement import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // Creates a proposal manager, and returns it in automata and white box form, along diff --git a/agreement/proposalTracker_test.go b/agreement/proposalTracker_test.go index 9cd9f07f70..5cfa4a3fe5 100644 --- a/agreement/proposalTracker_test.go +++ b/agreement/proposalTracker_test.go @@ -21,9 +21,10 @@ import ( "sort" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func sortedVoteGen(t *testing.T) (votes []vote) { diff --git a/agreement/service_test.go b/agreement/service_test.go index 27f35fba0f..4486ae8315 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -29,9 +29,10 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" diff --git a/agreement/sort_test.go b/agreement/sort_test.go index 87ac1594ff..83c7abb657 100644 --- a/agreement/sort_test.go +++ b/agreement/sort_test.go @@ -19,10 +19,11 @@ package agreement import ( "testing" + "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" - "github.com/stretchr/testify/require" ) func TestSortProposalValueLess(t *testing.T) { 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/classBasedPeerSelector.go b/catchup/classBasedPeerSelector.go index 3596f7c7f0..5506e45adc 100644 --- a/catchup/classBasedPeerSelector.go +++ b/catchup/classBasedPeerSelector.go @@ -20,8 +20,9 @@ import ( "errors" "time" - "github.com/algorand/go-algorand/network" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/network" ) // classBasedPeerSelector is a rankPooledPeerSelector that tracks and ranks classes of peers based on their response behavior. diff --git a/catchup/classBasedPeerSelector_test.go b/catchup/classBasedPeerSelector_test.go index ff1470f0c6..ce7ce89f06 100644 --- a/catchup/classBasedPeerSelector_test.go +++ b/catchup/classBasedPeerSelector_test.go @@ -20,9 +20,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // Use to mock the wrapped peer selectors where warranted diff --git a/catchup/fetcher_test.go b/catchup/fetcher_test.go index 8c5cd2aae1..43ad7bc06e 100644 --- a/catchup/fetcher_test.go +++ b/catchup/fetcher_test.go @@ -256,6 +256,11 @@ func (p *testUnicastPeer) RoutingAddr() []byte { } func (p *testUnicastPeer) Request(ctx context.Context, tag protocol.Tag, topics network.Topics) (resp *network.Response, e error) { + select { + case <-ctx.Done(): + return resp, ctx.Err() + default: + } responseChannel := make(chan *network.Response, 1) p.responseChannels[0] = responseChannel diff --git a/catchup/service_test.go b/catchup/service_test.go index e7993225ca..a8ac63eca7 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -26,10 +26,11 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" diff --git a/cmd/algocfg/main.go b/cmd/algocfg/main.go index f503514703..5e2be37ad7 100644 --- a/cmd/algocfg/main.go +++ b/cmd/algocfg/main.go @@ -20,8 +20,9 @@ import ( "fmt" "os" - "github.com/algorand/go-algorand/cmd/util/datadir" "github.com/spf13/cobra" + + "github.com/algorand/go-algorand/cmd/util/datadir" ) func init() { diff --git a/cmd/algocfg/profileCommand_test.go b/cmd/algocfg/profileCommand_test.go index e9914279b7..4d53588c4e 100644 --- a/cmd/algocfg/profileCommand_test.go +++ b/cmd/algocfg/profileCommand_test.go @@ -19,10 +19,9 @@ package main import ( "testing" - "github.com/algorand/go-algorand/config" - "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/test/partitiontest" ) diff --git a/cmd/algod/main.go b/cmd/algod/main.go index 4b1a3a2ed8..a96b6a5473 100644 --- a/cmd/algod/main.go +++ b/cmd/algod/main.go @@ -29,6 +29,10 @@ import ( "strings" "time" + "github.com/gofrs/flock" + + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/daemon/algod" @@ -41,9 +45,6 @@ import ( "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/metrics" "github.com/algorand/go-algorand/util/tokens" - "github.com/gofrs/flock" - - "github.com/algorand/go-deadlock" ) var dataDirectory = flag.String("d", "", "Root Algorand daemon data path") diff --git a/cmd/algofix/deadlock_test.go b/cmd/algofix/deadlock_test.go index 2e13c4b7bc..475d14bc69 100644 --- a/cmd/algofix/deadlock_test.go +++ b/cmd/algofix/deadlock_test.go @@ -25,8 +25,9 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) const deadlockSimpleSrc = `package main diff --git a/cmd/algoh/blockWatcher_test.go b/cmd/algoh/blockWatcher_test.go index 8d3d2662ac..c271abef6f 100644 --- a/cmd/algoh/blockWatcher_test.go +++ b/cmd/algoh/blockWatcher_test.go @@ -22,10 +22,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func bw(client Client) *blockWatcher { diff --git a/cmd/algoh/blockstats_test.go b/cmd/algoh/blockstats_test.go index cf848e96f4..b67128bcfc 100644 --- a/cmd/algoh/blockstats_test.go +++ b/cmd/algoh/blockstats_test.go @@ -20,13 +20,14 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "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/telemetryspec" "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) type event struct { diff --git a/cmd/algokey/keyreg.go b/cmd/algokey/keyreg.go index 262dc43653..48358daf01 100644 --- a/cmd/algokey/keyreg.go +++ b/cmd/algokey/keyreg.go @@ -94,7 +94,7 @@ func init() { "mainnet": mustConvertB64ToDigest("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="), "testnet": mustConvertB64ToDigest("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="), "betanet": mustConvertB64ToDigest("mFgazF+2uRS1tMiL9dsj01hJGySEmPN28B/TjjvpVW0="), - "devnet": mustConvertB64ToDigest("sC3P7e2SdbqKJK0tbiCdK9tdSpbe6XeCGKdoNzmlj0E="), + "devnet": mustConvertB64ToDigest("sjkznd5fmOPzTzMi6BAHa2Ir9DyOxu5H7NH3ratQG1w="), } validNetworkList = slices.Collect(maps.Keys(validNetworks)) } diff --git a/cmd/catchpointdump/bench_report.go b/cmd/catchpointdump/bench_report.go index 970486e926..483c779cf4 100644 --- a/cmd/catchpointdump/bench_report.go +++ b/cmd/catchpointdump/bench_report.go @@ -24,9 +24,10 @@ import ( "runtime" "time" - "github.com/algorand/go-algorand/util" "github.com/google/uuid" "github.com/klauspost/cpuid/v2" + + "github.com/algorand/go-algorand/util" ) type benchStage struct { diff --git a/cmd/catchpointdump/file.go b/cmd/catchpointdump/file.go index 50c68fd440..75b750a846 100644 --- a/cmd/catchpointdump/file.go +++ b/cmd/catchpointdump/file.go @@ -33,6 +33,7 @@ import ( "github.com/spf13/cobra" "github.com/algorand/avm-abi/apps" + cmdutil "github.com/algorand/go-algorand/cmd/util" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" diff --git a/cmd/catchupsrv/download_test.go b/cmd/catchupsrv/download_test.go index bf80605300..5a90a2b6ed 100644 --- a/cmd/catchupsrv/download_test.go +++ b/cmd/catchupsrv/download_test.go @@ -19,8 +19,9 @@ package main import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestBlockToPath(t *testing.T) { diff --git a/cmd/catchupsrv/main.go b/cmd/catchupsrv/main.go index 6bfdae0f9f..b2b06d12e2 100644 --- a/cmd/catchupsrv/main.go +++ b/cmd/catchupsrv/main.go @@ -26,9 +26,10 @@ import ( "path" "strconv" - "github.com/algorand/websocket" "github.com/gorilla/mux" + "github.com/algorand/websocket" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" diff --git a/cmd/catchupsrv/tarblocks.go b/cmd/catchupsrv/tarblocks.go index a74b445806..97206b7ade 100644 --- a/cmd/catchupsrv/tarblocks.go +++ b/cmd/catchupsrv/tarblocks.go @@ -26,8 +26,9 @@ import ( "strconv" "strings" - "github.com/algorand/go-algorand/logging" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/logging" ) type tarBlockSet struct { diff --git a/cmd/dispenser/server.go b/cmd/dispenser/server.go index e6f314a334..a87ee089f8 100644 --- a/cmd/dispenser/server.go +++ b/cmd/dispenser/server.go @@ -18,12 +18,10 @@ package main import ( _ "embed" - "html" - - // "bytes" "encoding/json" "flag" "fmt" + "html" "io" "log" "net/http" diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 01614a49a1..45c8c7c671 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -32,6 +32,7 @@ import ( "github.com/algorand/avm-abi/abi" "github.com/algorand/avm-abi/apps" + "github.com/algorand/go-algorand/cmd/util/datadir" "github.com/algorand/go-algorand/crypto" apiclient "github.com/algorand/go-algorand/daemon/algod/api/client" diff --git a/cmd/goal/application_test.go b/cmd/goal/application_test.go index bb5d7e1bf7..c3375a01c0 100644 --- a/cmd/goal/application_test.go +++ b/cmd/goal/application_test.go @@ -21,9 +21,10 @@ import ( "slices" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestParseMethodArgJSONtoByteSlice(t *testing.T) { diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 23c4488b15..4a93692d57 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -27,6 +27,8 @@ import ( "strings" "time" + "github.com/spf13/cobra" + "github.com/algorand/go-algorand/cmd/util/datadir" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -41,8 +43,6 @@ import ( "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util" - - "github.com/spf13/cobra" ) var ( diff --git a/cmd/goal/clerk_test.go b/cmd/goal/clerk_test.go index c590bdb46c..52306f6d77 100644 --- a/cmd/goal/clerk_test.go +++ b/cmd/goal/clerk_test.go @@ -20,8 +20,9 @@ import ( "path/filepath" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func abs(t *testing.T, path string) string { diff --git a/cmd/goal/commands_test.go b/cmd/goal/commands_test.go index 2cebf09eda..238c820561 100644 --- a/cmd/goal/commands_test.go +++ b/cmd/goal/commands_test.go @@ -20,9 +20,10 @@ import ( "os" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/cmd/util/datadir" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestEnsureDataDirReturnsWhenDataDirIsProvided(t *testing.T) { // nolint:paralleltest // Sets shared OS environment variable. diff --git a/cmd/goal/formatting_test.go b/cmd/goal/formatting_test.go index bb87249dc8..faba1aedef 100644 --- a/cmd/goal/formatting_test.go +++ b/cmd/goal/formatting_test.go @@ -19,9 +19,10 @@ package main import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestUnicodePrintable(t *testing.T) { diff --git a/cmd/goal/node_test.go b/cmd/goal/node_test.go index 5ab6b3b53a..0873e52412 100644 --- a/cmd/goal/node_test.go +++ b/cmd/goal/node_test.go @@ -22,10 +22,11 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/ledger/ledgercore" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/test/partitiontest" ) var isNum = regexp.MustCompile(`^[0-9]+$`) diff --git a/cmd/goal/p2pid.go b/cmd/goal/p2pid.go index 2ccdfdc571..8201e44827 100644 --- a/cmd/goal/p2pid.go +++ b/cmd/goal/p2pid.go @@ -23,12 +23,13 @@ import ( "os" "path" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/spf13/cobra" + "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{ diff --git a/cmd/goal/tealsign.go b/cmd/goal/tealsign.go index 74b718ece2..a7b6f40a13 100644 --- a/cmd/goal/tealsign.go +++ b/cmd/goal/tealsign.go @@ -21,13 +21,13 @@ import ( "encoding/base64" "os" + "github.com/spf13/cobra" + "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/data/transactions/logic" "github.com/algorand/go-algorand/protocol" - - "github.com/spf13/cobra" ) var ( diff --git a/cmd/netgoal/commands.go b/cmd/netgoal/commands.go index c87f060816..172b1e9cd1 100644 --- a/cmd/netgoal/commands.go +++ b/cmd/netgoal/commands.go @@ -20,10 +20,10 @@ import ( "fmt" "os" - "github.com/algorand/go-deadlock" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" + + "github.com/algorand/go-deadlock" ) var log *logrus.Logger diff --git a/cmd/tealdbg/cdtdbg.go b/cmd/tealdbg/cdtdbg.go index ea38efff7f..297864888c 100644 --- a/cmd/tealdbg/cdtdbg.go +++ b/cmd/tealdbg/cdtdbg.go @@ -23,9 +23,10 @@ import ( "net/http" "time" - "github.com/algorand/go-deadlock" "github.com/gorilla/mux" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/cmd/tealdbg/cdt" ) diff --git a/cmd/tealdbg/dryrunRequest.go b/cmd/tealdbg/dryrunRequest.go index e89e8cfc3d..bf1d4e6a05 100644 --- a/cmd/tealdbg/dryrunRequest.go +++ b/cmd/tealdbg/dryrunRequest.go @@ -17,13 +17,13 @@ package main import ( + "fmt" "log" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/protocol" - v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/protocol" ) // ddrFromParams converts serialized DryrunRequest to v2.DryrunRequest @@ -69,8 +69,12 @@ func balanceRecordsFromDdr(ddr *v2.DryrunRequest) (records []basics.BalanceRecor return } // deserialize app params and update account data + if a.Params == nil { + err = fmt.Errorf("application %d has no params", a.Id) + return + } var params basics.AppParams - params, err = v2.ApplicationParamsToAppParams(&a.Params) + params, err = v2.ApplicationParamsToAppParams(a.Params) if err != nil { return } diff --git a/cmd/tealdbg/local_test.go b/cmd/tealdbg/local_test.go index c781e1d3c0..1e8ac5d566 100644 --- a/cmd/tealdbg/local_test.go +++ b/cmd/tealdbg/local_test.go @@ -27,6 +27,9 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/data/basics" @@ -35,8 +38,6 @@ import ( "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var txnSample string = `{ @@ -1060,7 +1061,7 @@ func TestLocalBalanceAdapterIndexer(t *testing.T) { case strings.HasPrefix(r.URL.Path, accountPath): w.WriteHeader(200) if r.URL.Path[len(accountPath):] == brs.Addr.String() { - account, err := v2.AccountDataToAccount(brs.Addr.String(), &brs.AccountData, 100, &config.ConsensusParams{MinBalance: 100000}, basics.MicroAlgos{Raw: 0}) + account, err := v2.AccountDataToAccount(brs.Addr.String(), &brs.AccountData, 100, &config.ConsensusParams{MinBalance: 100000}, basics.MicroAlgos{Raw: 0}, v2.AccountDataToAccountOptions{}) a.NoError(err) accountResponse := AccountIndexerResponse{Account: account, CurrentRound: 100} response, err := json.Marshal(accountResponse) diff --git a/cmd/tealdbg/server.go b/cmd/tealdbg/server.go index 216c746e24..64131ad30e 100644 --- a/cmd/tealdbg/server.go +++ b/cmd/tealdbg/server.go @@ -25,9 +25,11 @@ import ( "strings" "time" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/websocket" "github.com/gorilla/mux" + + "github.com/algorand/websocket" + + "github.com/algorand/go-algorand/data/basics" ) const ( diff --git a/cmd/tealdbg/server_test.go b/cmd/tealdbg/server_test.go index 04c03725de..b0106429d8 100644 --- a/cmd/tealdbg/server_test.go +++ b/cmd/tealdbg/server_test.go @@ -22,9 +22,10 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/gorilla/mux" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type mockFactory struct{} diff --git a/cmd/tealdbg/webdbg.go b/cmd/tealdbg/webdbg.go index 9d4dde8b44..e15e4a3220 100644 --- a/cmd/tealdbg/webdbg.go +++ b/cmd/tealdbg/webdbg.go @@ -25,10 +25,12 @@ import ( "log" "net/http" - "github.com/algorand/go-algorand/protocol" + "github.com/gorilla/mux" + "github.com/algorand/go-deadlock" "github.com/algorand/websocket" - "github.com/gorilla/mux" + + "github.com/algorand/go-algorand/protocol" ) // WebPageFrontend is web page debugging frontend diff --git a/cmd/tealdbg/webdbg_test.go b/cmd/tealdbg/webdbg_test.go index cb36742821..2a229a70a4 100644 --- a/cmd/tealdbg/webdbg_test.go +++ b/cmd/tealdbg/webdbg_test.go @@ -23,9 +23,10 @@ import ( "net/http/httptest" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/gorilla/mux" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestWebPageFrontendHandlers(t *testing.T) { diff --git a/config/consensus_test.go b/config/consensus_test.go index 53af3a9aaa..296cb41b1e 100644 --- a/config/consensus_test.go +++ b/config/consensus_test.go @@ -19,8 +19,9 @@ package config import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestConsensusParams(t *testing.T) { diff --git a/config/dnsbootstrap_test.go b/config/dnsbootstrap_test.go index 43caa73134..885ccf6565 100644 --- a/config/dnsbootstrap_test.go +++ b/config/dnsbootstrap_test.go @@ -17,14 +17,15 @@ package config import ( - "github.com/algorand/go-algorand/protocol" - "pgregory.net/rapid" "strings" "testing" + "github.com/stretchr/testify/assert" + "pgregory.net/rapid" + "github.com/algorand/go-algorand/internal/rapidgen" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" ) /** diff --git a/config/version.go b/config/version.go index 45955101fa..0a5c3a7040 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 4 // 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 = 5 +const VersionMinor = 6 // Version is the type holding our full version information. type Version struct { diff --git a/config/version_test.go b/config/version_test.go index bf289d65de..e491993d87 100644 --- a/config/version_test.go +++ b/config/version_test.go @@ -20,9 +20,10 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/s3" - "github.com/stretchr/testify/require" ) func TestAlgodVsUpdatedVersions(t *testing.T) { diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index a4d5d6a32f..74053f9fea 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -39,6 +39,7 @@ package crypto // size_t num, // int *valid_p); import "C" + import ( "errors" "unsafe" diff --git a/crypto/batchverifier_bench_test.go b/crypto/batchverifier_bench_test.go index 0fc983a831..7ba4d49ef6 100644 --- a/crypto/batchverifier_bench_test.go +++ b/crypto/batchverifier_bench_test.go @@ -21,8 +21,9 @@ import ( "io" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func randSignedMsg(t testing.TB, r io.Reader) (SignatureVerifier, Hashable, Signature) { diff --git a/crypto/encoding_test.go b/crypto/encoding_test.go index 1a72fcad2d..6761f0be17 100644 --- a/crypto/encoding_test.go +++ b/crypto/encoding_test.go @@ -20,8 +20,6 @@ import ( "testing" "github.com/algorand/go-algorand/test/partitiontest" - // "github.com/stretchr/testify/require" - // "github.com/algorand/go-algorand/protocol" ) func TestEmptyEncoding(t *testing.T) { diff --git a/crypto/falconWrapper_test.go b/crypto/falconWrapper_test.go index a105ed8845..b30c9f8881 100644 --- a/crypto/falconWrapper_test.go +++ b/crypto/falconWrapper_test.go @@ -19,9 +19,11 @@ package crypto import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/falcon" + "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestSignAndVerifyFalcon(t *testing.T) { diff --git a/crypto/gobatchverifier_test.go b/crypto/gobatchverifier_test.go index a096ec5fe7..1049ed78a7 100644 --- a/crypto/gobatchverifier_test.go +++ b/crypto/gobatchverifier_test.go @@ -31,10 +31,11 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" ) // ensure internal ed25519 types match the expected []byte lengths used by ed25519consensus package diff --git a/crypto/hashes.go b/crypto/hashes.go index 039c38d239..2b0be41ca6 100644 --- a/crypto/hashes.go +++ b/crypto/hashes.go @@ -23,8 +23,9 @@ import ( "fmt" "hash" - "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-sumhash" + + "github.com/algorand/go-algorand/protocol" ) // HashType represents different hash functions diff --git a/crypto/hashes_test.go b/crypto/hashes_test.go index 0f748322e9..3a29780cc4 100644 --- a/crypto/hashes_test.go +++ b/crypto/hashes_test.go @@ -19,9 +19,9 @@ package crypto import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestHashFactoryCreatingNewHashes(t *testing.T) { diff --git a/crypto/merklearray/proof.go b/crypto/merklearray/proof.go index 5ea9fabf1d..9311298d03 100644 --- a/crypto/merklearray/proof.go +++ b/crypto/merklearray/proof.go @@ -19,8 +19,9 @@ package merklearray import ( "fmt" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/crypto" ) // Proof is used to convince a verifier about membership of leaves: h0,h1...hn diff --git a/crypto/merkletrie/bitset_test.go b/crypto/merkletrie/bitset_test.go index f65d7a818b..1d0d6231a9 100644 --- a/crypto/merkletrie/bitset_test.go +++ b/crypto/merkletrie/bitset_test.go @@ -20,8 +20,9 @@ import ( "math/bits" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestBitSet(t *testing.T) { diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index 6aee8ae2da..14f3f4e292 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -20,9 +20,10 @@ import ( "encoding/binary" "fmt" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-deadlock" ) // A OneTimeSignature is a cryptographic signature that is produced a limited diff --git a/crypto/secp256k1/scalar_mult_cgo.go b/crypto/secp256k1/scalar_mult_cgo.go index d25046fd53..444f6e62bc 100644 --- a/crypto/secp256k1/scalar_mult_cgo.go +++ b/crypto/secp256k1/scalar_mult_cgo.go @@ -6,11 +6,6 @@ package secp256k1 -import ( - "math/big" - "unsafe" -) - /* #include "libsecp256k1/include/secp256k1.h" @@ -20,6 +15,11 @@ extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned */ import "C" +import ( + "math/big" + "unsafe" +) + // ScalarMult func func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) { // Ensure scalar is exactly 32 bytes. We pad always, even if diff --git a/crypto/stateproof/weights_test.go b/crypto/stateproof/weights_test.go index 5620b66fb3..c571f36dfd 100644 --- a/crypto/stateproof/weights_test.go +++ b/crypto/stateproof/weights_test.go @@ -21,10 +21,11 @@ import ( "math" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config/bounds" "github.com/algorand/go-algorand/crypto/merklearray" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestMaxNumberOfRevealsInVerify(t *testing.T) { diff --git a/crypto/util_test.go b/crypto/util_test.go index 7c5184ae4e..3214264331 100644 --- a/crypto/util_test.go +++ b/crypto/util_test.go @@ -20,9 +20,10 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestEncodeDecode(t *testing.T) { diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index b775d6fe41..6f566f007c 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -190,11 +190,15 @@ }, { "name": "exclude", - "description": "When set to `all` will exclude asset holdings, application local state, created asset parameters, any created application parameters. Defaults to `none`.", + "description": "Exclude additional items from the account. Use `all` to exclude asset holdings, application local state, created asset parameters, and created application parameters. Use `created-apps-params` to exclude only the parameters of created applications (returns only application IDs). Use `created-assets-params` to exclude only the parameters of created assets (returns only asset IDs). Multiple values can be comma-separated (e.g., `created-apps-params,created-assets-params`). Note: `all` and `none` cannot be combined with other values. Defaults to `none`.", "in": "query", "required": false, - "type": "string", - "enum": ["all", "none"] + "type": "array", + "items": { + "type": "string", + "enum": ["all", "none", "created-apps-params", "created-assets-params"] + }, + "collectionFormat": "csv" }, { "$ref": "#/parameters/format" @@ -278,7 +282,7 @@ "/v2/accounts/{address}/assets": { "get": { "description": "Lookup an account's asset holdings.", - "tags": ["public", "experimental"], + "tags": ["public", "nonparticipating"], "produces": ["application/json"], "schemes": ["http"], "summary": "Get a list of assets held by an account, inclusive of asset params.", @@ -322,6 +326,56 @@ } } }, + "/v2/accounts/{address}/applications": { + "get": { + "description": "Lookup an account's application holdings (local state and params if the account is the creator).", + "tags": ["public", "nonparticipating"], + "produces": ["application/json"], + "schemes": ["http"], + "summary": "Get a list of applications held by an account.", + "operationId": "AccountApplicationsInformation", + "parameters": [ + { + "$ref": "#/parameters/address" + }, + { + "$ref": "#/parameters/limit" + }, + { + "$ref": "#/parameters/next" + }, + { + "$ref": "#/parameters/include" + } + ], + "responses": { + "200": { + "$ref": "#/responses/AccountApplicationsInformationResponse" + }, + "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" + } + } + } + }, "/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.", @@ -2653,6 +2707,35 @@ } } }, + "AccountApplicationResource": { + "description": "AccountApplicationResource describes the account's application resource (local state and params if the account is the creator) for a specific application ID.", + "type": "object", + "required": ["id"], + "properties": { + "id": { + "description": "The application ID.", + "type": "integer", + "x-go-type": "basics.AppIndex" + }, + "created-at-round": { + "description": "Round when the account opted into or created the application.", + "type": "integer", + "x-go-type": "basics.Round" + }, + "deleted": { + "description": "Whether the application has been deleted.", + "type": "boolean" + }, + "app-local-state": { + "description": "\\[appl\\] the application local data stored in this account.\n\nThe raw account uses `AppLocalState` for this type.", + "$ref": "#/definitions/ApplicationLocalState" + }, + "params": { + "description": "\\[appp\\] parameters of the application created by this account including app global data.\n\nThe raw account uses `AppParams` for this type.\nOnly present if the account is the creator and `include=params` is specified.", + "$ref": "#/definitions/ApplicationParams" + } + } + }, "AccountParticipation": { "description": "AccountParticipation describes the parameters used by this account in consensus protocol.", "type": "object", @@ -2699,7 +2782,7 @@ "Asset": { "description": "Specifies both the unique identifier and the parameters for an asset", "type": "object", - "required": ["index", "params"], + "required": ["index"], "properties": { "index": { "description": "unique asset identifier", @@ -3067,7 +3150,7 @@ "Application": { "description": "Application index and its parameters", "type": "object", - "required": ["id", "params"], + "required": ["id"], "properties": { "id": { "description": "\\[appidx\\] application index.", @@ -4191,6 +4274,18 @@ "type": "string", "name": "tx-type", "in": "query" + }, + "include": { + "name": "include", + "description": "Include additional items in the response. Use `params` to include full application parameters (global state, schema, etc.). Multiple values can be comma-separated. Defaults to returning only application IDs and local state.", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string", + "enum": ["params"] + }, + "collectionFormat": "csv" } }, "responses": { @@ -4313,6 +4408,30 @@ } } }, + "AccountApplicationsInformationResponse": { + "description": "AccountApplicationsInformationResponse contains a list of application resources for an account.", + "schema": { + "type": "object", + "required": ["round"], + "properties": { + "round": { + "description": "The round for which this information is relevant.", + "type": "integer", + "x-go-type": "basics.Round" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter. The next token is the next application ID to use as the pagination cursor.", + "type": "string" + }, + "application-resources": { + "type": "array", + "items": { + "$ref": "#/definitions/AccountApplicationResource" + } + } + } + } + }, "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": { @@ -4676,7 +4795,7 @@ "schema": { "description": "Supply represents the current supply of MicroAlgos in the system", "type": "object", - "required": ["online-money", "current_round", "total-money"], + "required": ["online-money", "current_round", "total-money", "online-stake"], "properties": { "current_round": { "description": "Round", @@ -4684,7 +4803,12 @@ "x-go-type": "basics.Round" }, "online-money": { - "description": "OnlineMoney", + "description": "Total stake held by accounts with status Online at current_round, including those whose participation keys have expired but have not yet been marked offline.", + "type": "integer", + "x-go-type": "uint64" + }, + "online-stake": { + "description": "Online stake used by agreement to vote for current_round, excluding accounts whose participation keys have expired.", "type": "integer", "x-go-type": "uint64" }, diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 7331a93cbd..8fe08aa12c 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -62,6 +62,22 @@ "type": "string" } }, + "include": { + "description": "Include additional items in the response. Use `params` to include full application parameters (global state, schema, etc.). Multiple values can be comma-separated. Defaults to returning only application IDs and local state.", + "explode": false, + "in": "query", + "name": "include", + "schema": { + "items": { + "enum": [ + "params" + ], + "type": "string" + }, + "type": "array" + }, + "style": "form" + }, "limit": { "description": "Maximum number of results to return.", "in": "query", @@ -171,6 +187,36 @@ }, "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." }, + "AccountApplicationsInformationResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "application-resources": { + "items": { + "$ref": "#/components/schemas/AccountApplicationResource" + }, + "type": "array" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter. The next token is the next application ID to use as the pagination cursor.", + "type": "string" + }, + "round": { + "description": "The round for which this information is relevant.", + "type": "integer", + "x-go-type": "basics.Round" + } + }, + "required": [ + "round" + ], + "type": "object" + } + } + }, + "description": "AccountApplicationsInformationResponse contains a list of application resources for an account." + }, "AccountAssetResponse": { "content": { "application/json": { @@ -874,7 +920,12 @@ "x-go-type": "basics.Round" }, "online-money": { - "description": "OnlineMoney", + "description": "Total stake held by accounts with status Online at current_round, including those whose participation keys have expired but have not yet been marked offline.", + "type": "integer", + "x-go-type": "uint64" + }, + "online-stake": { + "description": "Online stake used by agreement to vote for current_round, excluding accounts whose participation keys have expired.", "type": "integer", "x-go-type": "uint64" }, @@ -887,6 +938,7 @@ "required": [ "current_round", "online-money", + "online-stake", "total-money" ], "type": "object" @@ -1148,6 +1200,35 @@ ], "type": "object" }, + "AccountApplicationResource": { + "description": "AccountApplicationResource describes the account's application resource (local state and params if the account is the creator) for a specific application ID.", + "properties": { + "app-local-state": { + "$ref": "#/components/schemas/ApplicationLocalState" + }, + "created-at-round": { + "description": "Round when the account opted into or created the application.", + "type": "integer", + "x-go-type": "basics.Round" + }, + "deleted": { + "description": "Whether the application has been deleted.", + "type": "boolean" + }, + "id": { + "description": "The application ID.", + "type": "integer", + "x-go-type": "basics.AppIndex" + }, + "params": { + "$ref": "#/components/schemas/ApplicationParams" + } + }, + "required": [ + "id" + ], + "type": "object" + }, "AccountAssetHolding": { "description": "AccountAssetHolding describes the account's asset holding and asset parameters (if either exist) for a specific asset ID.", "properties": { @@ -1267,8 +1348,7 @@ } }, "required": [ - "id", - "params" + "id" ], "type": "object" }, @@ -1483,8 +1563,7 @@ } }, "required": [ - "index", - "params" + "index" ], "type": "object" }, @@ -3107,16 +3186,23 @@ "x-go-type": "basics.Address" }, { - "description": "When set to `all` will exclude asset holdings, application local state, created asset parameters, any created application parameters. Defaults to `none`.", + "description": "Exclude additional items from the account. Use `all` to exclude asset holdings, application local state, created asset parameters, and created application parameters. Use `created-apps-params` to exclude only the parameters of created applications (returns only application IDs). Use `created-assets-params` to exclude only the parameters of created assets (returns only asset IDs). Multiple values can be comma-separated (e.g., `created-apps-params,created-assets-params`). Note: `all` and `none` cannot be combined with other values. Defaults to `none`.", + "explode": false, "in": "query", "name": "exclude", "schema": { - "enum": [ - "all", - "none" - ], - "type": "string" - } + "items": { + "enum": [ + "all", + "none", + "created-apps-params", + "created-assets-params" + ], + "type": "string" + }, + "type": "array" + }, + "style": "form" }, { "description": "Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON.", @@ -3204,6 +3290,131 @@ ] } }, + "/v2/accounts/{address}/applications": { + "get": { + "description": "Lookup an account's application holdings (local state and params if the account is the creator).", + "operationId": "AccountApplicationsInformation", + "parameters": [ + { + "description": "An account public key.", + "in": "path", + "name": "address", + "required": true, + "schema": { + "pattern": "[A-Z0-9]{58}", + "type": "string", + "x-go-type": "basics.Address" + }, + "x-go-type": "basics.Address" + }, + { + "description": "Maximum number of results to return.", + "in": "query", + "name": "limit", + "schema": { + "type": "integer", + "x-go-type": "uint64" + }, + "x-go-type": "uint64" + }, + { + "description": "The next page of results. Use the next token provided by the previous results.", + "in": "query", + "name": "next", + "schema": { + "type": "string" + } + }, + { + "description": "Include additional items in the response. Use `params` to include full application parameters (global state, schema, etc.). Multiple values can be comma-separated. Defaults to returning only application IDs and local state.", + "explode": false, + "in": "query", + "name": "include", + "schema": { + "items": { + "enum": [ + "params" + ], + "type": "string" + }, + "type": "array" + }, + "style": "form" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "application-resources": { + "items": { + "$ref": "#/components/schemas/AccountApplicationResource" + }, + "type": "array" + }, + "next-token": { + "description": "Used for pagination, when making another request provide this token with the next parameter. The next token is the next application ID to use as the pagination cursor.", + "type": "string" + }, + "round": { + "description": "The round for which this information is relevant.", + "type": "integer", + "x-go-type": "basics.Round" + } + }, + "required": [ + "round" + ], + "type": "object" + } + } + }, + "description": "AccountApplicationsInformationResponse contains a list of application resources for 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 applications held by an account.", + "tags": [ + "public", + "nonparticipating" + ] + } + }, "/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.", @@ -3456,7 +3667,7 @@ "summary": "Get a list of assets held by an account, inclusive of asset params.", "tags": [ "public", - "experimental" + "nonparticipating" ] } }, @@ -5521,7 +5732,12 @@ "x-go-type": "basics.Round" }, "online-money": { - "description": "OnlineMoney", + "description": "Total stake held by accounts with status Online at current_round, including those whose participation keys have expired but have not yet been marked offline.", + "type": "integer", + "x-go-type": "uint64" + }, + "online-stake": { + "description": "Online stake used by agreement to vote for current_round, excluding accounts whose participation keys have expired.", "type": "integer", "x-go-type": "uint64" }, @@ -5534,6 +5750,7 @@ "required": [ "current_round", "online-money", + "online-stake", "total-money" ], "type": "object" diff --git a/daemon/algod/api/server/lib/middlewares/auth_test.go b/daemon/algod/api/server/lib/middlewares/auth_test.go index 8ef7900376..937fec4e57 100644 --- a/daemon/algod/api/server/lib/middlewares/auth_test.go +++ b/daemon/algod/api/server/lib/middlewares/auth_test.go @@ -21,9 +21,10 @@ import ( "net/http" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/labstack/echo/v4" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) var errSuccess = errors.New("unexpected success") diff --git a/daemon/algod/api/server/lib/middlewares/cors_test.go b/daemon/algod/api/server/lib/middlewares/cors_test.go index 05ac524960..edad86f15f 100644 --- a/daemon/algod/api/server/lib/middlewares/cors_test.go +++ b/daemon/algod/api/server/lib/middlewares/cors_test.go @@ -21,9 +21,10 @@ import ( "net/http/httptest" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestMakeCORS(t *testing.T) { diff --git a/daemon/algod/api/server/router.go b/daemon/algod/api/server/router.go index acfc946dbb..e0b9adf0ed 100644 --- a/daemon/algod/api/server/router.go +++ b/daemon/algod/api/server/router.go @@ -22,10 +22,9 @@ import ( "net" "net/http" - "golang.org/x/sync/semaphore" - "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "golang.org/x/sync/semaphore" "github.com/algorand/go-algorand/daemon/algod/api/server/common" "github.com/algorand/go-algorand/daemon/algod/api/server/lib" diff --git a/daemon/algod/api/server/v2/account.go b/daemon/algod/api/server/v2/account.go index 7089b19bf9..3de8a01d66 100644 --- a/daemon/algod/api/server/v2/account.go +++ b/daemon/algod/api/server/v2/account.go @@ -39,11 +39,18 @@ func AssetHolding(ah basics.AssetHolding, ai basics.AssetIndex) model.AssetHoldi } } +// AccountDataToAccountOptions specifies options for converting AccountData to Account +type AccountDataToAccountOptions struct { + ExcludeCreatedAppsParams bool + ExcludeCreatedAssetsParams bool +} + // AccountDataToAccount converts basics.AccountData to v2.model.Account func AccountDataToAccount( address string, record *basics.AccountData, lastRound basics.Round, consensus *config.ConsensusParams, amountWithoutPendingRewards basics.MicroAlgos, + opts AccountDataToAccountOptions, ) (model.Account, error) { assets := make([]model.AssetHolding, 0, len(record.Assets)) @@ -60,7 +67,14 @@ func AccountDataToAccount( createdAssets := make([]model.Asset, 0, len(record.AssetParams)) for idx, params := range record.AssetParams { - asset := AssetParamsToAsset(address, idx, ¶ms) + var asset model.Asset + if opts.ExcludeCreatedAssetsParams { + asset = model.Asset{ + Index: idx, + } + } else { + asset = AssetParamsToAsset(address, idx, ¶ms) + } createdAssets = append(createdAssets, asset) } sort.Slice(createdAssets, func(i, j int) bool { @@ -84,7 +98,14 @@ func AccountDataToAccount( createdApps := make([]model.Application, 0, len(record.AppParams)) for appIdx, appParams := range record.AppParams { - app := AppParamsToApplication(address, appIdx, &appParams) + var app model.Application + if opts.ExcludeCreatedAppsParams { + app = model.Application{ + Id: appIdx, + } + } else { + app = AppParamsToApplication(address, appIdx, &appParams) + } createdApps = append(createdApps, app) } sort.Slice(createdApps, func(i, j int) bool { @@ -224,6 +245,9 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) { if a.CreatedAssets != nil && len(*a.CreatedAssets) > 0 { assetParams = make(map[basics.AssetIndex]basics.AssetParams, len(*a.CreatedAssets)) for _, ca := range *a.CreatedAssets { + if ca.Params == nil { + continue + } var metadataHash [32]byte if ca.Params.MetadataHash != nil { copy(metadataHash[:], *ca.Params.MetadataHash) @@ -293,11 +317,13 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) { if a.CreatedApps != nil && len(*a.CreatedApps) > 0 { appParams = make(map[basics.AppIndex]basics.AppParams, len(*a.CreatedApps)) for _, params := range *a.CreatedApps { - ap, err := ApplicationParamsToAppParams(¶ms.Params) - if err != nil { - return basics.AccountData{}, err + if params.Params != nil { + ap, err := ApplicationParamsToAppParams(params.Params) + if err != nil { + return basics.AccountData{}, err + } + appParams[params.Id] = ap } - appParams[params.Id] = ap } } @@ -408,7 +434,7 @@ func AppParamsToApplication(creator string, appIdx basics.AppIndex, appParams *b extraProgramPages := uint64(appParams.ExtraProgramPages) app := model.Application{ Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator, ApprovalProgram: appParams.ApprovalProgram, ClearStateProgram: appParams.ClearStateProgram, @@ -468,6 +494,6 @@ func AssetParamsToAsset(creator string, idx basics.AssetIndex, params *basics.As return model.Asset{ Index: idx, - Params: assetParams, + Params: &assetParams, } } diff --git a/daemon/algod/api/server/v2/account_test.go b/daemon/algod/api/server/v2/account_test.go index 173a7cbf80..8528cadcaf 100644 --- a/daemon/algod/api/server/v2/account_test.go +++ b/daemon/algod/api/server/v2/account_test.go @@ -106,7 +106,7 @@ func TestAccount(t *testing.T) { b := a.WithUpdatedRewards(proto.RewardUnit, 100) addr := basics.Address{}.String() - conv, err := AccountDataToAccount(addr, &b, round, &proto, a.MicroAlgos) + conv, err := AccountDataToAccount(addr, &b, round, &proto, a.MicroAlgos, AccountDataToAccountOptions{}) require.NoError(t, err) require.Equal(t, addr, conv.Address) require.Equal(t, b.MicroAlgos.Raw, conv.Amount) @@ -206,7 +206,7 @@ func TestAccount(t *testing.T) { // convert the same account a few more times to make sure we always // produce the same model.Account for i := 0; i < 10; i++ { - anotherConv, err := AccountDataToAccount(addr, &b, round, &proto, a.MicroAlgos) + anotherConv, err := AccountDataToAccount(addr, &b, round, &proto, a.MicroAlgos, AccountDataToAccountOptions{}) require.NoError(t, err) require.Equal(t, protocol.EncodeJSON(conv), protocol.EncodeJSON(anotherConv)) @@ -223,7 +223,7 @@ func TestAccountRandomRoundTrip(t *testing.T) { for addr, acct := range accts { round := basics.Round(2) proto := config.Consensus[protocol.ConsensusFuture] - conv, err := AccountDataToAccount(addr.String(), &acct, round, &proto, acct.MicroAlgos) + conv, err := AccountDataToAccount(addr.String(), &acct, round, &proto, acct.MicroAlgos, AccountDataToAccountOptions{}) require.NoError(t, err) c, err := AccountToAccountData(&conv) require.NoError(t, err) diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index c940709296..504cfac480 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -27,10 +27,9 @@ 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" "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/ledgercore" - - "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/protocol" ) @@ -101,6 +100,9 @@ func (dr *DryrunRequest) ExpandSources() error { case "approv", "clearp": for ai, app := range dr.Apps { if app.Id == s.AppIndex { + if dr.Apps[ai].Params == nil { + dr.Apps[ai].Params = &model.ApplicationParams{} + } switch s.FieldName { case "approv": dr.Apps[ai].Params.ApprovalProgram = ops.Program @@ -116,6 +118,19 @@ func (dr *DryrunRequest) ExpandSources() error { return nil } +// ValidateApps ensures all applications have params set. +// This should be called after ExpandSources to ensure that either: +// 1. The caller provided params explicitly, or +// 2. Sources populated the params +func (dr *DryrunRequest) ValidateApps() error { + for _, app := range dr.Apps { + if app.Params == nil { + return fmt.Errorf("application %d does not have params set", app.Id) + } + } + return nil +} + type dryrunDebugReceiver struct { disassembly string lines []string @@ -229,6 +244,9 @@ func (dl *dryrunLedger) init() error { } for i, app := range dl.dr.Apps { var addr basics.Address + if app.Params == nil { + continue + } if app.Params.Creator != "" { var err error addr, err = basics.UnmarshalChecksumAddress(app.Params.Creator) @@ -275,20 +293,22 @@ func (dl *dryrunLedger) lookup(rnd basics.Round, addr basics.Address) (basics.Ac appi, ok := dl.accountApps[addr] if ok { app := dl.dr.Apps[appi] - params, err := ApplicationParamsToAppParams(&app.Params) - if err != nil { - return basics.AccountData{}, 0, err - } - if out.AppParams == nil { - out.AppParams = make(map[basics.AppIndex]basics.AppParams) - out.AppParams[app.Id] = params - } else { - ap, ok := out.AppParams[app.Id] - if ok { - MergeAppParams(&ap, ¶ms) - out.AppParams[app.Id] = ap - } else { + if app.Params != nil { + params, err := ApplicationParamsToAppParams(app.Params) + if err != nil { + return basics.AccountData{}, 0, err + } + if out.AppParams == nil { + out.AppParams = make(map[basics.AppIndex]basics.AppParams) out.AppParams[app.Id] = params + } else { + ap, ok := out.AppParams[app.Id] + if ok { + MergeAppParams(&ap, ¶ms) + out.AppParams[app.Id] = ap + } else { + out.AppParams[app.Id] = params + } } } } @@ -424,6 +444,13 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) { return } + // Validate that all apps have params after Sources expansion + err = dr.ValidateApps() + if err != nil { + response.Error = err.Error() + return + } + dl := dryrunLedger{dr: dr} err = dl.init() if err != nil { @@ -525,8 +552,8 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) { var app basics.AppParams ok := false for _, appt := range dr.Apps { - if appt.Id == appIdx { - app, err = ApplicationParamsToAppParams(&appt.Params) + if appt.Id == appIdx && appt.Params != nil { + app, err = ApplicationParamsToAppParams(appt.Params) if err != nil { response.Error = err.Error() return diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index f4f1d044b8..afbf897dd0 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -174,6 +174,26 @@ int not_an_int`, require.Contains(t, response.Error, "5:4: unable to parse \"not_an_int\" as integer") } +func TestDryrunAppWithoutParams(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Test that an app without params and without Sources fails validation + dr := DryrunRequest{ + Apps: []model.Application{ + { + Id: 1007, + // No Params, no Sources - should fail validation + }, + }, + } + var response model.DryrunResponse + + doDryrunRequest(&dr, &response) + require.NotEmpty(t, response.Error) + require.Contains(t, response.Error, "application 1007 does not have params set") +} + func TestDryrunLogicSig(t *testing.T) { partitiontest.PartitionTest(t) // {"txns":[{"lsig":{"l":"AiABASI="},"txn":{}}]} @@ -497,7 +517,7 @@ func TestDryrunGlobal1(t *testing.T) { dr.Apps = []model.Application{ { Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: globalTestProgram, GlobalState: &gkv, GlobalStateSchema: &model.ApplicationStateSchema{ @@ -547,7 +567,7 @@ func TestDryrunGlobal2(t *testing.T) { dr.Apps = []model.Application{ { Id: 1234, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: globalTestProgram, GlobalState: &gkv, }, @@ -594,7 +614,7 @@ func TestDryrunLocal1(t *testing.T) { dr.Apps = []model.Application{ { Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: localStateCheckProg, LocalStateSchema: &model.ApplicationStateSchema{ NumByteSlice: 10, @@ -668,7 +688,7 @@ func TestDryrunLocal1A(t *testing.T) { dr.Apps = []model.Application{ { Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ LocalStateSchema: &model.ApplicationStateSchema{ NumByteSlice: 10, NumUint: 10, @@ -746,7 +766,7 @@ func TestDryrunLocalCheck(t *testing.T) { dr.Apps = []model.Application{ { Id: 1234, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: localStateCheckProg, }, }, @@ -806,7 +826,7 @@ func TestDryrunMultipleTxns(t *testing.T) { dr.Apps = []model.Application{ { Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: globalTestProgram, GlobalState: &gkv, GlobalStateSchema: &model.ApplicationStateSchema{ @@ -851,7 +871,7 @@ func TestDryrunEncodeDecode(t *testing.T) { gdr.Apps = []model.Application{ { Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: localStateCheckProg, }, }, @@ -961,7 +981,7 @@ func TestDryrunMakeLedger(t *testing.T) { dr.Apps = []model.Application{ { Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: sender.String(), ApprovalProgram: localStateCheckProg, }, @@ -1156,7 +1176,7 @@ int 1`) Apps: []model.Application{ { Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator.String(), ApprovalProgram: approval, ClearStateProgram: clst, @@ -1247,7 +1267,7 @@ return Apps: []model.Application{ { Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator.String(), ApprovalProgram: approval, ClearStateProgram: clst, @@ -1256,7 +1276,7 @@ return }, { Id: appIdx + 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator.String(), ApprovalProgram: approv, ClearStateProgram: clst, @@ -1384,7 +1404,7 @@ int 1`) Apps: []model.Application{ { Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator.String(), ApprovalProgram: app1, ClearStateProgram: clst, @@ -1393,7 +1413,7 @@ int 1`) }, { Id: appIdx + 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator.String(), ApprovalProgram: app2, ClearStateProgram: clst, @@ -1402,7 +1422,7 @@ int 1`) }, { Id: appIdx + 2, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator.String(), ApprovalProgram: app3, ClearStateProgram: clst, @@ -1484,7 +1504,7 @@ int 1` }.SignedTxn()}, Apps: []model.Application{{ Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: sender.String(), ApprovalProgram: approval, ClearStateProgram: clst, @@ -1546,7 +1566,7 @@ int 0 Apps: []model.Application{ { Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ Creator: creator.String(), ApprovalProgram: approval, ClearStateProgram: clst, @@ -1611,7 +1631,7 @@ int 1 }.SignedTxn()}, Apps: []model.Application{{ Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: paySender.Program, ClearStateProgram: clst, }, @@ -1687,7 +1707,7 @@ int 1`) ApplicationID: appIdx}.SignedTxn()) apps = append(apps, model.Application{ Id: appIdx, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: approvalOps.Program, ClearStateProgram: clst, }, @@ -1806,7 +1826,7 @@ int %d`, expectedUint, i)) dr.Apps = []model.Application{ { Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: ops.Program, GlobalStateSchema: &model.ApplicationStateSchema{ NumByteSlice: 1, @@ -1861,7 +1881,7 @@ func TestDryrunEarlyExit(t *testing.T) { } dr.Apps = []model.Application{{ Id: 1, - Params: model.ApplicationParams{ + Params: &model.ApplicationParams{ ApprovalProgram: ops.Program, }, }} diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go index fdd7e23287..f3ec27a816 100644 --- a/daemon/algod/api/server/v2/generated/data/routes.go +++ b/daemon/algod/api/server/v2/generated/data/routes.go @@ -115,226 +115,232 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "jp5OH4/u/l8AAAD//2Q3BRCzDQEA", + "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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 41ab0d5c51..b197616e66 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -8,23 +8,17 @@ 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/go-algorand/data/basics" "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" - "github.com/oapi-codegen/runtime" ) // 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 basics.Address, params AccountAssetsInformationParams) error // Returns OK if experimental API is enabled. // (GET /v2/experimental) ExperimentalCheck(ctx echo.Context) error @@ -38,40 +32,6 @@ 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 basics.Address - - err = runtime.BindStyledParameterWithOptions("simple", "address", ctx.Param("address"), &address, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - 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 unmarshaled 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 @@ -122,7 +82,6 @@ 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...) @@ -131,231 +90,233 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "EAEA", } // 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 53bb1417ae..65bc0b1bc0 100644 --- a/daemon/algod/api/server/v2/generated/model/types.go +++ b/daemon/algod/api/server/v2/generated/model/types.go @@ -53,8 +53,10 @@ const ( // Defines values for AccountInformationParamsExclude. const ( - AccountInformationParamsExcludeAll AccountInformationParamsExclude = "all" - AccountInformationParamsExcludeNone AccountInformationParamsExclude = "none" + AccountInformationParamsExcludeAll AccountInformationParamsExclude = "all" + AccountInformationParamsExcludeCreatedAppsParams AccountInformationParamsExclude = "created-apps-params" + AccountInformationParamsExcludeCreatedAssetsParams AccountInformationParamsExclude = "created-assets-params" + AccountInformationParamsExcludeNone AccountInformationParamsExclude = "none" ) // Defines values for AccountInformationParamsFormat. @@ -63,6 +65,11 @@ const ( AccountInformationParamsFormatMsgpack AccountInformationParamsFormat = "msgpack" ) +// Defines values for AccountApplicationsInformationParamsInclude. +const ( + AccountApplicationsInformationParamsIncludeParams AccountApplicationsInformationParamsInclude = "params" +) + // Defines values for AccountApplicationInformationParamsFormat. const ( AccountApplicationInformationParamsFormatJson AccountApplicationInformationParamsFormat = "json" @@ -244,6 +251,24 @@ type Account struct { // * lsig type AccountSigType string +// AccountApplicationResource AccountApplicationResource describes the account's application resource (local state and params if the account is the creator) for a specific application ID. +type AccountApplicationResource struct { + // AppLocalState Stores local state associated with an application. + AppLocalState *ApplicationLocalState `json:"app-local-state,omitempty"` + + // CreatedAtRound Round when the account opted into or created the application. + CreatedAtRound *basics.Round `json:"created-at-round,omitempty"` + + // Deleted Whether the application has been deleted. + Deleted *bool `json:"deleted,omitempty"` + + // Id The application ID. + Id basics.AppIndex `json:"id"` + + // Params Stores the global information associated with an application. + Params *ApplicationParams `json:"params,omitempty"` +} + // 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. @@ -308,7 +333,7 @@ type Application struct { Id basics.AppIndex `json:"id"` // Params Stores the global information associated with an application. - Params ApplicationParams `json:"params"` + Params *ApplicationParams `json:"params,omitempty"` } // ApplicationInitialStates An application's initial global/local/box states that were accessed during simulation. @@ -424,7 +449,7 @@ type Asset struct { // // Definition: // data/transactions/asset.go : AssetParams - Params AssetParams `json:"params"` + Params *AssetParams `json:"params,omitempty"` } // AssetHolding Describes an asset held by an account. @@ -1087,6 +1112,9 @@ type Catchpoint = string // Format defines model for format. type Format string +// Include defines model for include. +type Include = []string + // Limit defines model for limit. type Limit = uint64 @@ -1120,6 +1148,17 @@ type AccountApplicationResponse struct { Round basics.Round `json:"round"` } +// AccountApplicationsInformationResponse defines model for AccountApplicationsInformationResponse. +type AccountApplicationsInformationResponse struct { + ApplicationResources *[]AccountApplicationResource `json:"application-resources,omitempty"` + + // NextToken Used for pagination, when making another request provide this token with the next parameter. The next token is the next application ID to use as the pagination cursor. + NextToken *string `json:"next-token,omitempty"` + + // Round The round for which this information is relevant. + Round basics.Round `json:"round"` +} + // AccountAssetResponse defines model for AccountAssetResponse. type AccountAssetResponse struct { // AssetHolding Describes an asset held by an account. @@ -1397,9 +1436,12 @@ type SupplyResponse struct { // CurrentRound Round CurrentRound basics.Round `json:"current_round"` - // OnlineMoney OnlineMoney + // OnlineMoney Total stake held by accounts with status Online at current_round, including those whose participation keys have expired but have not yet been marked offline. OnlineMoney uint64 `json:"online-money"` + // OnlineStake Online stake used by agreement to vote for current_round, excluding accounts whose participation keys have expired. + OnlineStake uint64 `json:"online-stake"` + // TotalMoney TotalMoney TotalMoney uint64 `json:"total-money"` } @@ -1444,8 +1486,8 @@ type VersionsResponse = Version // AccountInformationParams defines parameters for AccountInformation. type AccountInformationParams struct { - // Exclude When set to `all` will exclude asset holdings, application local state, created asset parameters, any created application parameters. Defaults to `none`. - Exclude *AccountInformationParamsExclude `form:"exclude,omitempty" json:"exclude,omitempty"` + // Exclude Exclude additional items from the account. Use `all` to exclude asset holdings, application local state, created asset parameters, and created application parameters. Use `created-apps-params` to exclude only the parameters of created applications (returns only application IDs). Use `created-assets-params` to exclude only the parameters of created assets (returns only asset IDs). Multiple values can be comma-separated (e.g., `created-apps-params,created-assets-params`). Note: `all` and `none` cannot be combined with other values. Defaults to `none`. + Exclude *[]AccountInformationParamsExclude `form:"exclude,omitempty" json:"exclude,omitempty"` // Format Configures whether the response object is JSON or MessagePack encoded. If not provided, defaults to JSON. Format *AccountInformationParamsFormat `form:"format,omitempty" json:"format,omitempty"` @@ -1457,6 +1499,21 @@ type AccountInformationParamsExclude string // AccountInformationParamsFormat defines parameters for AccountInformation. type AccountInformationParamsFormat string +// AccountApplicationsInformationParams defines parameters for AccountApplicationsInformation. +type AccountApplicationsInformationParams 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"` + + // Include Include additional items in the response. Use `params` to include full application parameters (global state, schema, etc.). Multiple values can be comma-separated. Defaults to returning only application IDs and local state. + Include *[]AccountApplicationsInformationParamsInclude `form:"include,omitempty" json:"include,omitempty"` +} + +// AccountApplicationsInformationParamsInclude defines parameters for AccountApplicationsInformation. +type AccountApplicationsInformationParamsInclude string + // AccountApplicationInformationParams defines parameters for AccountApplicationInformation. type AccountApplicationInformationParams 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 ce7f06a588..15eb973f1f 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go @@ -184,231 +184,238 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "ju7+/wAAAP//IsjI8GQcAQA=", } // 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 8ed5635845..53407e127c 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -25,9 +25,15 @@ type ServerInterface interface { // Get account information. // (GET /v2/accounts/{address}) AccountInformation(ctx echo.Context, address basics.Address, params AccountInformationParams) error + // Get a list of applications held by an account. + // (GET /v2/accounts/{address}/applications) + AccountApplicationsInformation(ctx echo.Context, address basics.Address, params AccountApplicationsInformationParams) error // Get account information about a given app. // (GET /v2/accounts/{address}/applications/{application-id}) AccountApplicationInformation(ctx echo.Context, address basics.Address, applicationId basics.AppIndex, params AccountApplicationInformationParams) error + // Get a list of assets held by an account, inclusive of asset params. + // (GET /v2/accounts/{address}/assets) + AccountAssetsInformation(ctx echo.Context, address basics.Address, params AccountAssetsInformationParams) error // Get account information about a given asset. // (GET /v2/accounts/{address}/assets/{asset-id}) AccountAssetInformation(ctx echo.Context, address basics.Address, assetId basics.AssetIndex, params AccountAssetInformationParams) error @@ -127,7 +133,7 @@ func (w *ServerInterfaceWrapper) AccountInformation(ctx echo.Context) error { var params AccountInformationParams // ------------- Optional query parameter "exclude" ------------- - err = runtime.BindQueryParameter("form", true, false, "exclude", ctx.QueryParams(), ¶ms.Exclude) + err = runtime.BindQueryParameter("form", false, false, "exclude", ctx.QueryParams(), ¶ms.Exclude) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter exclude: %s", err)) } @@ -144,6 +150,47 @@ func (w *ServerInterfaceWrapper) AccountInformation(ctx echo.Context) error { return err } +// AccountApplicationsInformation converts echo context to params. +func (w *ServerInterfaceWrapper) AccountApplicationsInformation(ctx echo.Context) error { + var err error + // ------------- Path parameter "address" ------------- + var address basics.Address + + err = runtime.BindStyledParameterWithOptions("simple", "address", ctx.Param("address"), &address, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + 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 AccountApplicationsInformationParams + // ------------- 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)) + } + + // ------------- Optional query parameter "include" ------------- + + err = runtime.BindQueryParameter("form", false, false, "include", ctx.QueryParams(), ¶ms.Include) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter include: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.AccountApplicationsInformation(ctx, address, params) + return err +} + // AccountApplicationInformation converts echo context to params. func (w *ServerInterfaceWrapper) AccountApplicationInformation(ctx echo.Context) error { var err error @@ -179,6 +226,40 @@ func (w *ServerInterfaceWrapper) AccountApplicationInformation(ctx echo.Context) return err } +// AccountAssetsInformation converts echo context to params. +func (w *ServerInterfaceWrapper) AccountAssetsInformation(ctx echo.Context) error { + var err error + // ------------- Path parameter "address" ------------- + var address basics.Address + + err = runtime.BindStyledParameterWithOptions("simple", "address", ctx.Param("address"), &address, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + 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 unmarshaled arguments + err = w.Handler.AccountAssetsInformation(ctx, address, params) + return err +} + // AccountAssetInformation converts echo context to params. func (w *ServerInterfaceWrapper) AccountAssetInformation(ctx echo.Context) error { var err error @@ -722,7 +803,9 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } router.GET(baseURL+"/v2/accounts/:address", wrapper.AccountInformation, m...) + router.GET(baseURL+"/v2/accounts/:address/applications", wrapper.AccountApplicationsInformation, m...) router.GET(baseURL+"/v2/accounts/:address/applications/:application-id", wrapper.AccountApplicationInformation, m...) + router.GET(baseURL+"/v2/accounts/:address/assets", wrapper.AccountAssetsInformation, m...) router.GET(baseURL+"/v2/accounts/:address/assets/:asset-id", wrapper.AccountAssetInformation, m...) router.GET(baseURL+"/v2/applications/:application-id", wrapper.GetApplicationByID, m...) router.GET(baseURL+"/v2/applications/:application-id/box", wrapper.GetApplicationBoxByName, m...) @@ -754,305 +837,322 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "9Gj6YPT+/wUAAP//psDgLSLlAQA=", } // 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 6110d048f0..29fe0d6e4f 100644 --- a/daemon/algod/api/server/v2/generated/participating/private/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go @@ -249,235 +249,242 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "Wp0bKwEA", } // 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 2ba1687f18..296bfee771 100644 --- a/daemon/algod/api/server/v2/generated/participating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go @@ -178,238 +178,244 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9e3Mbt5Io/lXw426VYy1JyY6TPfGvUnvlOA9t7NhlKdmH5ZuAM00SR0NgDoChyPjq", - "u99C4zGYGQw5lGQ72Zu/bHHwaDQajUY/348ysSoFB67V6On7UUklXYEGiX/RPJeg8L85qEyyUjPBR09H", - "p5zQLBMV16SsZgXLyBVsp6PxiJmvJdXL0XjE6QpGT8Mg45GEf1RMQj56qmUF45HKlrCidlqtQZq+b08n", - "/30y+erd+y/+djMaj/S2NGMoLRlfjMajzWQhJu7HGVUsU9NTN/7Nvq+0LAuWUbOECcvTi6qbEJYD12zO", - "QPYtrDnervWtGGerajV6ehKWxLiGBcieNZXlGc9h07eo6DNVCnTveszHASvxY9zrGsygO1fRaJBRnS1L", - "wbhOrITgV2I/J5cQdd+1iLmQK6rb7SPyQ9p7NH50cvNPgRQfjb/4PE2MtFgISXk+CeN+E8Yl57bdzQEN", - "/dc2Ar4RfM4WlQRFrpeglyCJXgKRoErBFRAx+ztkmjBF/v381U9ESPISlKILeE2zKwI8EznkU3I2J1xo", - "UkqxZjnkY5LDnFaFVkQL7Bno4x8VyG2NXQdXjEnghhbejv6uBB+NRyu1KGl2NXrXRtPNzXhUsBVLrOol", - "3RiKIrxazUASMTcL8uBI0JXkfQDZEWN4dpJkxbj+8kmbDutfV3TTBe9CVjyjGvIIQC0pVzQzLRDKnKmy", - "oFtE7Ypuvj4ZO8AVoUVBSuA54wuiN1z1LcXMfW8L4bBJIPpiCcR8ISVdQITnKflZAVISftXiCnigDjLb", - "4qdSwpqJSoVOPevAqRMLiehAioqnGBXBDw7NPTzK9r1PBvUGR7zZ/U2xhfvUhvqcLS62JZA5K8x9Sf5e", - "KR0IuFK47UsgqoTM8N6cmGEM8hVbcKorCU8v+ZH5i0zIuaY8pzI3v6zsTy+rQrNztjA/FfanF2LBsnO2", - "6NmBAGvqnCrstrL/mPHSR1VvknfJCyGuqjJeUBafBUMrZ8/7KMOO2U8aaQZ5GuQG3B831sXm7HkfS93d", - "Q2/CRvYA2Yu7kpqGV7CVYKCl2Rz/2cyRtOhc/j6y4oXprct5CrWG/B27RoHq1MpPp7UQ8cZ9Nl8zwTXY", - "qzASM46R2T59H0tOUpQgNbOD0rKcFCKjxURpqnGkf5YwHz0d/dNxLegd2+7qOJr8hel1jp3MZSzBML4J", - "LcsDxnhthEcUtXoOuuFD9qjPhSTXS5YtiV4yRRi3m4hyl+E0Bawp19PRQSf5JuYObx0Q9VbYS9JuRYsB", - "9e4FsQ1noJD2ndD7QDUkRcQ4QYwTynOyKMQs/PDZaVnWyMXvp2VpUTUmbE6A4X0OG6a0eoiYofUhi+c5", - "ez4l38djX7OiIIIXWzIDd+9Absa0fNvxcSeAG8TiGuoRHyiCOy3k1OyaR4ORy+6DGFGqXIrCXIF7ycg0", - "/sG1jSnQ/D6o85+e+mK099MdSvQOqUhN9pf64UY+axFVl6awh6Gm03bf21GUGWUHLamzGsH3TVf4C9Ow", - "UnuJJIIoIjS3PVRKuvUS1AQloS4F/azAEk9JF4wjtGMjkHOyold2PwTi3RACqCBpWzKz4tU108ta5Aqo", - "n3beF39uQk7tOTEbTpmRjUnBlDbCEG6mIksoUOCkQbEQU9GtiGYALexYRID5WtLSkrn7YuU4xgkN7y8L", - "6x1v8oGXbBLmWG1R4x2hujUz38twk5BYhUMThmeFyK5+oGp5D4d/5sfqHguchiyB5iDJkqpl4ky1aLse", - "bQh9m4ZIs2QWTTUNS3whFuoelliIQ7haWX5Di8JM3eVmrdXiwIMOclEQ05jAimnzAGYcT8CCrYFb1jMl", - "39JsaYQJktGiGNd6CVFOClhDQYQkjHOQY6KXVNeHH0f2DyU8RwoMH9RAotU4ncaUXCxBwlxIfKhKICuK", - "l9PKPI/KotknMFdFV9CSnfCyFJU2MEYvl7PnfnWwBo48KQyN4Ic14oM/Hnxq5nafcGYu7OKoBFS0MJ4V", - "VV7jL/CLBtCmdX3V8noKIXNU9FBtfmOSZELaIezl7yY3/wEq686WOj8rJUzcEJKuQSpamNW1FvUwkO99", - "nc49JzOnmkYn01Fh+kVnOQf2Q6EQZEK78Qr/QwtiPhsBx1BSTT0M5RSUacJ+4J1tUGVnMg0M39KCrKze", - "jJQ0uzoIym/qydNsZtDJ+9aq6twWukWEHbrYsFzd1zbhYH171TwhVufj2VFHTNnJdKK5hiDgQpTEso8W", - "CJZT4GgWIWJz79faM7FJwfRMbDpXmtjAveyEGWcws38mNs8dZELuxzyOPQTpZoGcrkDh7dYwg5hZalX1", - "6UzI20kTHdNErYAn1IwaCVPjFpKwaVVO3NlMqMdtg9ZAJKiXdgsB7eFTGGtg4VzTD4AFZUa9Dyw0B7pv", - "LIhVyQq4B9JfJoW4GVXw+WNy/sPpF48e//r4iy8NSZZSLCRdkdlWgyKfOT0fUXpbwMPkwwmli/ToXz7x", - "BpHmuKlxlKhkBitadoeyhhb7MLbNiGnXxVoTzbjqAOAgjgjmarNoJ29sv5vx6DnMqsU5aG0ewa+lmN87", - "N+zMkIIOG70upREsVNMo5aSl49w0OYaNlvS4xJbAc2t6M+tgyrwBV7N7Iaq+jc/rWXLiMJrD3kNx6DbV", - "02zjrZJbWd2H5gOkFDJ5BZdSaJGJYmLkPCYSuovXrgVxLfx2le3fLbTkmipi5kYDWMXzHhWF3vDh95cd", - "+mLDa9zsvMHsehOrc/MO2Zcm8utXSAlyojecIHU2NCdzKVaEkhw7oqzxPWgrf7EVnGu6Kl/N5/ejIxU4", - "UELFw1agzEzEtjDSj4JM8Fzt1eZ4a2ALmW6qIThrY8vbsnQ/VA5N51ueoRrpPs5yv/bLmfqI2vIsUoUZ", - "GAvIFw1a/aAqrz5MWSgeqASkBlMv8DNaBJ5Doel3Ql7U4u73UlTlvbPz9pxDl0PdYpzNITd9vUaZ8UUB", - "DUl9YWCfptb4SRb0TVA62DUg9EisL9hiqaP35WspPsAdmpwlBSh+sMqlwvTpqph+ErlhPrpS9yB61oPV", - "HNHQbcwH6UxUmlDCRQ64+ZVKC6U9XjvmoGaVlMB1LOeiPoMpMgNDXRmtzGqrkmiRul/qjhOa2RM6QdSo", - "HjeH4KphW9nplnQNhBYSaL4lMwBOxMwsuvZywEVSRUojOzuxzonEQ/ltA9hSigyUgnzi9Nl74fXt7P2j", - "dyAPV4OrCLMQJcicyg+zgqv1XuCvYDtZ06Iy4vmPv6iHf5RFaKFpsWcLsE1qI9rqu+5S7gDTLiJuQxST", - "stUW2pNgRGzDdArQ0Ifsu2Ovd/vbYHaI4AMhcA0SPWo+6NHyk3wAogzwf+CD9UGWUJUTIwb2qh+M5Gr2", - "m1MuvGy4Z4YwQUGVnuy7Ukyjht7ELDXi4qlbBAfukSdfUKVRDCSM56i/tVchzmNlSzPF6ECnMpyy9zVm", - "Jv3FP8S602bmeueqUuFVpqqyFFJDnloe2qx75/oJNmEuMY/GDk8/LUilYN/IfQiMxnd4dIoA/IPqYKF2", - "Nu/u4tDrwIgv20Ox3ICvxtEuGM99qwjxsVNtD4xM1XtgyY2pFr3NhCiAospUaVGWhkPpScVDvz4MntvW", - "p/rnum2XJK0ZyEoquQCFJibX3kF+bZGu0Na1pIo4OLx/Aiq8rItcF2ZzrCeK8Qwmu84LPoJNq/jg3Oq4", - "V+VC0hwmORR0m/C2sJ+J/XwgYfixkUBq/YHQMJmhNTFNI/WZ8P6mt5tV4FQqJXgT/EIyc87NM6omNdf7", - "9pPmgNOm+KYj1gdhFgQjSQd+PESWpafEiHj3r4U2ZOWIDlfjbqU7rqUHe2HWD4JAHHdSKwLas/8XKDd3", - "EMDudf4tqL6F11Pf17J71P94tzcuzNZV1rptkldEL1/ewxj7eFCPLeI1lZplrMTn6o+wvffXe3uCpK8E", - "yUFTVkBOog/2JV/G/Yl1Q26PebvX/CB1axf8jr41sRzvmdUE/gq2qDZ5bSMaIm3VfagjEqOaC5dygoB6", - "r3nz4ombwIZmutgawVYvYUuuQQJR1cx6rXRNaFqUk3iAdMxU/4zOIJ80h+/0EDjHoaLlpTwP7WtrN3wX", - "rSdXAx3ulVUKUST0n+0T30FGEoJB7kKkFGbXGS2KLdEhbMZTUgNId0GgN0aQZx6oBppxBeS/REUyyvGF", - "W2kIQpqQKPmgsGxmMOJmmNO5qtYYggJWYF/z+OXoqL3woyO350yROVxblxuODdvoODpCVdxroXTjcN2D", - "ttsct7PEpYO2SnPJuldbm6fsd3JzIw/ZydetwYOB05wppRzhmuXfmQG0TuZmyNpjGhnm4IfjDjLfNV3C", - "OuvGfT9nq6qg+j4MlbCmxUSsQUqWw15O7iZmgn+7psWr0O1mPIINZIZGM5hkGCU4cCy4MH1sYKEZh3Fm", - "DrANHBkKEJzZXue2056Xdu23zFYryBnVUGxJKSEDGyVnpFQVljolNmQiW1K+wBeQFNXCuTrbcZDhV8pq", - "wmTFO0McKorpDZ+gCUMlw9TQbOmjLY0QBtS8bNv2D/tYu6YBFHsZDbq0o+1p24OSJtPxqPfhb/C9rh/+", - "Fm/NkNHbGhMb8mGEtBqagdYzxKeRlbpIjLfRHD5DDB/GSlMPnYKyO3HkFF5/7PMLP6/Kstjeg5BkByIS", - "SgkKr7RYDajsVzEnL1kmxWmxEOHOU1ulYdU13tiuv/Yc1ze3eQELXjAOk5XgkHjSv8KvL/HjYLWjvYZ7", - "RkSB6KAB2w+fBhJaC2hOPoSk77pJSDLts9+2dKrvhLwvK7sdcPCbYoDleq9bh5vytvZ1WhQJk7RVP3S4", - "iBoHp3AmCVVKZAwFxbNcjZ33ubViW7f2Fvpfh9CoezjA7XFbttcoDMsq8qEoCSVZwVDNL7jSssr0Jaeo", - "6YuWmnAW9MqBfrXwN75JWg+dUBO7oS45RUfRoP9LOgbNIaGH+g7Aa4dVtViA0q0H1hzgkrtWjJOKM41z", - "rcxxmdjzUoJEj72pbbmiWzI3NKEF+R2kILNKN58cq0ppojQrCmcINtMQMb/kVJMCqNLkJeMXGxzO+5H4", - "I8tBXwt5FbAwHc64FsBBMTVJezp+b79iUInDydIFmGCshf3sPZ7r3BAjs/ZG0or//dm/PX17OvlvOvn9", - "ZPLVvxy/e//k5uFR58fHN19//X+aP31+8/XDf/vn1PZ52FPB4A7ys+fujX72HB9iUZxIG/Y/gkFmxfgk", - "SZSxQ1GLFslnmC/DEdzDpt5PL+GS6w03hLemBcsNL7o38mlfU50DbY9Yi8oaG9dS43kEHPgcugOrIglO", - "1eKvH0Sea0+w0+Em3vJWjIHjjOreAXQDp+Bqz5lyq33w/bcX5NgRgnqAxOKGjlILJF4wLoKx4eVjdikO", - "7Lrkl/w5zPE9KPjTS55TTY/taTquFMhntKA8g+lCkKc+KPI51fSSd66h3gRSUVBzlEEqxSnoKr2Wy8u3", - "tFiIy8t3HT+Ermzlpoq5qDtnXTWZn3Ji5AZR6YlL4jKRcE1lyhbiU3y4aGjsvRMOK5OIyiqxfJIYN/50", - "KJRlqdrJHrooKsvCoCgiVeXyFZhtJUqLEDhmmLmLvTU08JNwTiWSXvsnb6VAkd9WtHzLuH5HJpfVycnn", - "GIJXpzj4zfFAQ7fbEgY/fHuTUbTfu7hwK5ejU/mkpIuUzeTy8q0GWiKFoMCxwpdmURDs1ggP9JEAOFS9", - "gBCLfMCWWMgOjuvF5Z7bXj6tV3pR+Ak3tRk7facdjKLib72BeyLraaWXE8MRkqtS5hj4vfIJBujCXDne", - "g0CxBT4A1FJUZslAsiVkVy6zFaxKvR03untHF3cXe4bDFOqMXHDgnBn8ZZSbAasyp06QoXzbTnGjbDAE", - "DvoGrmB7IWz36cDsYFE2uijFiuo7uki70V1ryDc+yG6M9uY7vysfI+rSkWDcpSeLp4EufJ/+o20FgHs4", - "1imiaOT56EMElQlEWOLvQcEtFmrGuxPpp5bHeAZcszVMoGALNisSbPo/unYND6uhSgkZsLWP6g0DKsLm", - "xLyOZvY6di8mSfkCzKVuLmKhaIFO+9OkoR+lwyVQqWdA9U59LY/TTHjoUCC/xqBpVJqMzRJgY/abaVSC", - "cLg2Dzx8e9s2zpF4eit3KrsmyG8Jqu9eB0lPb/OIcAhP5LPz933Yk/BecP5pMXUiyPb7yuBwIcW12U0D", - "oPCpGzHBS3RPVYouYOh11DAVDUyJ0bAA4SD7pJ+kvCPmbbGmI2MMXITtPjF4SXIHMF8Me0AzQMvF0c9t", - "TYjOqvCKF1uP1FmBAnVwELWkQ2XDzsYXhwGbZmMgeS2sesCaWIuP/pIqf/TzccTRbyktfppUMrvy551F", - "3ndUd7Pj+Wu6zdrHVp8zAyK46eGz6PnUeT5f3mh8UO678ciFOKT2TnCUonMoYGFxYht7OqvzM9W7aeB4", - "NZ8j05ukHPkiZWQkmbg5wDzEjgixGnMyeITUKYjARss6Dkx+EvFh54tDgOQuvxT1Y+PdFf0N6WBB641v", - "pGRRmluf9VitMs9SXHqLWuRpuTjjMITxMTGcdE0Lw0ld4Gk9SCdXG759WpnZnG/Hw7430cCD5taI0slB", - "q7TyzG3WFwvefhnpV8FBa5iJzcRGRiefVrPNzJyJZLwCxmmnDq/NnPdAkZnYoE8R3nDWwf1g6Poh84BF", - "biAbppDKsV+f2GjBOwyQ3YJ8ipoVkp7TqwWy65NkbwdMjzjdR3afRSn07gmklgKzTgPuNDp79SxNaasr", - "idTX7Thkhw1hailW03c4kzvZg9Gu8rSZ6+6HOt1hf3I0f1Y/SpK/rlLuLnkZbefS5lo8JC1jmxwaQOzA", - "6uu2EJtEa9NxqYnXCGsplmQYfdfY1UWbggJQEzBpyNWTq5RZ+vLyrQKUGc59t0jPibtH+fZh5A0nYcGU", - "htq44J1cPr7tB9WJ5rEl5v2r06Wcm/W9ESIIGtYcix0by/zoK0DX9TmTSk/QMpNcgmn0nUJN2nemaVoQ", - "bvrbMWVNPQfLwQjRFWwnOSuqNCk7kH58biD6KdxcqprhRcm49TaaYSr8pIPuAbZJhMc6du9E0AuLoBf0", - "Y+Bn2MEyTQ1M0lBec/o/yRFr8cJdnCVByyli6m5oL0p38Noolr7LaCMhOnK7mO6y+XTOZe7H3uuN5SP6", - "+4QIO1JyLVFGxHQAoVgsIPeZ3lxQqM165fLpFYIv6lyC5vcd6QOnxGbxwyR8O/L3Ofd06HNOb5QTwaoY", - "SejjxwxCXkfXYe5BnGQB3GZuGR1eb6RIIi52jMcWkWb04/L2jtt80nX4ouUuXPv02j0Mm43bUwDN3bNK", - "gV/f7kPb3S6HunGf03EjRezuA4YDIsUxrSIBpkM0PZybliXLNy3Dnx11eguSGCjudTPBt3CGbMkNtgc/", - "TcfiPbV6HpjbEds7Y8cxPvOPzSPT+jM7j1xzNmjmsg3klURrUsNbuJtPPzw0B679x1/OtZB0Ac4iOLEg", - "3WkIXM4haIhS0iuimXWQztl8DrElTN3GitMArmPvyAcQdg8Jds1l4W25kz67RLaHtuoV7Edomp4SlNLn", - "c3HRtUf6h0ekWwuXTbRxtzAqJhMK/AjbyS+0qMxLiElV+6Y6A2HzWj+AJtarH2GLI+91+TSA7dkVVMW9", - "AaTQlHUlfFJRlvAHqlF9Ad/AjS08YKdO07t0T1vjSmn0H436hmrUk2gu5cMdm9pFxkA6ZK/O014n5mxB", - "c1vahL5vi1i+X/aJniDxVAy9N25zyYVMG3u9y4AWnvBxsaOb8ehu/h6pe9KNuGcnXoerObkL6I1p7f8N", - "p68DN4SWpRRrWkycn0yf0CHF2gkd2Ny71Xzk91X6VFx8e/ritQP/ZjzKCqByElQdvavCduWfZlW2BMfu", - "a8imY3e6XasKizY/pMyOPWmuMfV6S5vWqXVT+01FB9V51szTnuJ7+aZz8bJL3OHqBWXw9Kot0tbRq+nc", - "RdeUFd7w66EdqmW3yx1WXSnJJ+IB7uwkFnn/3XksxX6HCbqYih4HLRXw625G55LKDC4xGM5i22K5TRsv", - "n705fPN7gxcuL9+uPTi1kcd6b4U8/QkHP3VL9+sOA0wzkPoA7mHbiPxXmF41/RjkLvkqcmvnBUfvXTj9", - "TsjG7elCLZNedB9OajUvHIvHtKfAhXMN6MiqU2Ll2t8WvxmGdXQUU9zR0Zj8VrgPEYD4+8z9jo+7o6Ok", - "tTqpfzR8FNWLnK7gYQjW6N2Ij6sb4XA9TIY5Xa+C4C76yTBQqHWH8+i+dti7lszhM3e/5FCA+Wk6RH8S", - "b7pFdwzMkBN03hcqGTyyV7bGqCKCtxMDYOiuIS28D11ZEWv87x4hXq3QGD5RBcvSnkh8hhySWz9j05hg", - "48GGbTNHxXqc3XnFotFNM3UrO2xrIdGsSYSrZHriGr8z4VhAxdk/KohqDeMV0JIY/PsMR+1I/Wmlpxu4", - "Xcp4dJsqxHe3W3pV3y4t1k478PNgm/SISBW/OjAII56xw/x3BFA4ivLXJ0bbLZ0/817K2vn43F2Z2tmm", - "Pft0ZuD+V5ur0Wk38/mQnWZqMpfid0jLDmi5TOQT8SZ3hlaB34GnHGfbjCy4M9RVtOvZ9xHIcIVHH6nc", - "WcHhFx1K+d3mCk/zicM2+kBNRrTf/boMlc557jah7/Uce8M0o3t6mBke2MhXHQsMeR88yu0Jtck2GuFw", - "6XMeR68e2/Hrc+5g7kT8FvR6RlPVl8wj1sAUbX/DW1AL4jv7DVIhX4SdnUQBFqEtsxkIS5C1Saubv/mW", - "D1I77eCnaP3yRIqL35xj60BTKJEYpuLXlKNzI/azHND1VmCdQ0yvayEx66hKOzbmkLFVUkN/efk2z7ru", - "aDlbMFvnvFJA6Fy75JNuIFvp3lKRKzEeEqQ41JzNycm4PrN+N3K2ZvgiwxaPbIsZVXhBB0eN0MUsD7he", - "Kmz+eEDzZcVzCbleKotYJUhQGqDoGdxzZ6CvATg5wXaPviKfoRezYmt4mL5gnLA2evroq/Guct6Icaxc", - "v4vJ58jlfXRFmrLR1duOYdiqGzUdLjGXAL9D/32y43zZrkNOF7Z0V9D+07WinBqEpGBa7YHJ9sX9Rf+S", - "Fl64NRmB0lJsCdPp+UFTw7F6QtwNQ7RgkEysVkyvnPuqEitDYXVtdDupHw6L/vnabB4u/xH9wsvEG/8T", - "PLfoqifsEl39f0IngBitY0JtGtmC1UEhvmwuOfPpsrFYXahRZ3Fj5jJLR3kVY0TmpJSMa1RlVXo++Zt5", - "vkuaGYY47QN3MvvySaLoW7MuEj8M8I+OdwkK5DqNetlD9l7KcX3JZ1zwycpwlPxhnWciOpW9Duxpp+M+", - "X+ieoe8sXZtxJ70EWDUIkEbc/E6kyHcMeEfiDOs5iEIPXtlHp9VKpgmGVmaHfn7zwkkiKyFT5TdqBuCk", - "EglaMlhj0Gt6k8yYd9wLWQzahbtA/2ld7rxYGolu/nQnHwuRqTvxTgu5noyk/8vLOmk/WtxtMHFLeylk", - "Qk/rNI4f2Vf2MH1h27BvfRTxWw/mBqMNR+lipScGxQaZhD6fwgmtDZLd84aq9NFvRJp3PMr6R0cI9NHR", - "2InKvz1ufrbs/ehouB9vWl9ofk2g5nZ3TTulpumb2upnIqG986VFgzOby5+S0LAm7zJzpc7cGGPSrN/4", - "8eWO+wmiPNg3On2APGrwcxs3n5i/4mbWYTn9/KFZ0jZJPnn4HgV2UPJMbIYSUeva8vT0B0BRD0oGagVx", - "JZ2SvUn3jb2+RxHZmlFnUAjzUo2rcg12pfkT7YJBzXjHXlSsyH+prdCtm0lSni2Tnu4z0/FX+wyIGkQa", - "jGxJOYci2du+ln/1r+rEu//vomfYFePpT+3q0Bb2FqQ1WE0g/JR+fIMrpgszQYyiZpawkHelWIic4Dx1", - "OZWaNXbLrKfK2yYSD+Cwq0o7V2nM6OCqnMxZgb69aXs4tpxIqnu4qsR44Hk9IqyNnGLVEnZ0kISyFV7b", - "iq7KAvAQrkHSBXYVHFrdMY0cjhzVSiGqNJ+wJWakEURXkhMxn0fLAK6ZhGI7JiVVyg5yYpYFG5x79PTR", - "ycnJMCMj4mvA2i1e/cJf1Yt7dIxN7BdXjsxWcTgI/NtAf1NT3SGb3yUuVxP2HxUonWKx+MFGiaOF2Nzr", - "th5sqF08Jd9j0jRD6I26BagU9Wmfm4lKq7IQNB9jpuqLb09fEDur7SMBUYf1aBeoAWwekaSRZ3jiVp8U", - "rieh1vBxdufzMatWehIqxabSO5oWdYFb1nLJQt1gjJ0peW7VssGxx05CMN+5XEEeFaa1agAkDvMfrWm2", - "RH3ndLRTpdxTomh4XWXPAWtzURSMG6p4IQc3y3CllW1l5TERegnyminAZBiwhmYWyZCC1SnkfVbJ5mpl", - "xbklnOkB0muo2XXoLnjgrOjr/SuSkLX24c62vzq9CFZeP7QC9Tn2SgcTtcpZt/webB2Pja8EMiUvnbEj", - "o1xwlmEFjJQIjvkhh5lVBxQLSds71cid5cQxTBbRDlHzDou9ZbU9y3SI6zo1RF/NflvCsX9q2LjKhAvQ", - "yvFAyMe+pr0z0DGuwFVlM/QVc1QhE65fyVid4EJyj37y4xGmeOvRtX5nvv3kdPOYyOaKcdS5OaS6l6A1", - "sBWKoZ2dE6bJQoByq20Gq6m3ps/0YsMRhHfTF2LBsnO2wDGsK6JBinVN7g516h2VnWOwafuNaesKKoSf", - "Gy51dlK/7ndJFqLC/qcKwfeiP+X75R1pIuSG8ePRdhDjzvgDvJcNGcIaPf+gxPu8Qzahpn5zlG/Nk9XS", - "G7YgNpw4mcuY8QQYLxj3Bt90cq4seZfgxuBp7umnMkm1fXQM4ngXQIueGB2M9LceA3cdql0ewqAE1+jn", - "6N/Giw13tS162EpoUL8uKN8SfygMdUdCyTe0CB76ieL+KJ05Ycw6C7fK/afYimHrEx8v3EDX3ujU0B1L", - "tBx6T/WlQJ1V+QL0hOZ5KhneM/xK8KuPcoQNZFWoTBaCX5s55LvU5ibKBFfVasdcvsEdp8uZokrBalYk", - "XG+fh4+Qhx3G7FizLf6bKsvVvzPOE//gkHTvdp8fVjihG2Kfkp4NTU8UW0yGYwLvlLujo576doRe979X", - "SvfR6H+IYPMWl4v3KMXfvjUXR5w7vOPjb6+WkNob/ekFfvdJykJ62SZXwqusU3wOPTJw8xJb1gLeN0wC", - "vqZFTxqI2Gpj71dryehLBpH15jqh2qXU05TUPGGICqM/KZn1wG5ZhrrmzT4fa+ti/SGNJw4fO5Heb2n8", - "sWFXtF5vNUPptSfezuRXE8GhNj9XH6KrL6VFIbLBnMENc2o69ecPFquVS8ef8Mpbr0Qen4XYmwsgzdis", - "w3IitAIftslv+LRKfpHX6dEa+pFANENTqSEa3RLGNlrUg+eBsVPHE0UqW4dZ8h0rsGLVv5+/+mnUv5HR", - "DnS31OXzTqqw+zYmhM+1yWMhGvjYwQMEL9L6b9WjUseEVenT4EomJz98ZxWEQ0CyyZsOaf1i6OAdAlgI", - "W6oqVcyjmzJnVG+HR35EDfX2Wo4SU0eKKtoloBJvH6v0rJuQUB11ULXUhow0pOJUqriReyl4Day9aFyS", - "PFvxqVMsqsNAnw8RDjv4uBmPzvKDxKdUgayRHSXFYF+wxVI/K0R29QPQHKQtcpJ6TtoSJyswz1C1ZCW+", - "f0qhWF2kuDCDueziSxxuOjQ052IJLlWNz1zQGcs7UK8h01i0unYDlQDD/RzK9BINBN6giE0+gSuIBMih", - "1MudwpJ17i71sq5lCi7yjCkyA2e6WAMfEzaFaTtYLa8zVZEC6NwrYaUQekCx3xC2hGiMgU7RV6dw9G4x", - "sJOILsqzaOv7TodXhjkNMQE20PKaqjqdVSu3w+AY8vkcMszCvzMn4H8sgUdJ4sZedYewzKMUgSyEC2Id", - "iXvVaNew7srOtxPUqFDWh4S0L0vHFWwfKNKgoWSZ4hBhe5u09Igca8f1lQ76TBvOMZKpQE+IIO8H76oC", - "1IWfblOZIEqZeUswPI2b66lOo3k7aLxEcwswTNcDJ+3N0YeCaV/KwW7J9/6X8nOssK+cUykNOfBjfRI5", - "69aIvnY59DH7Y7AW+mz6oPxvPmusnaVgV65sDiLM2mavqcx9i3vJ3WfvTZYGeh5mZnVgVNfL51C/HBuh", - "mBXCCECTvsDQZqRScOF9oKyvdZ1JDaGeg5SQB5tgIRRMtPBhVgdkJHXhkzuwZ73Mb4W3lkf/ASHDdkW9", - "hR3e1NUtsEYlxUIO1Dmfx1ghElbUQC+jihNpNei+HfrGfveJTnzNwd3q1T68h3Oxv2y3D71jqoP5+HTN", - "iRMODuZejewot9DMMs5BTrwRt11vgjdzd2Ky57zKrKgSn82gvR6cC20HN0sqNbPuKltPqCgrxxVsj63a", - "x5dC9zseA21lSAt6lOW6RRT3qqtWKbgX9wLep80pWgpRTHosg2fdIhntw3DFsivAbLEhMsVIwQ+ax8ZM", - "Qj5Dg1TwGblebn0JiLIEDvnDKSGn3EYHeveRZlnU1uT8gd41/wZnzStb9sZpoKeXPB1mheVn5B25nx9m", - "B8/r400KDL+84/x2kFvMrje8z0fuGuvUNIsXT4eqN7r+HS0RKiI/C0VKgDq3huBvkCUk3lEEs7NEaYTQ", - "P4ASZ0AmqhApL/zbZJAxQ6UxFU+GAGngA56rNRRu8CQCnJPdnlSx7rNPhirmRELtm3HbrLAu0apl4qpP", - "NdKeOczS5IxzISGeEf1MbfboENmGyZfxPzOmJZXb2+RubaIqpYbqxfJeb8ngKFkvpHaW7OKwKMT1BNna", - "JJR8SqkDTDvVvLZ98dS6nznqM4jcLqlyIuKWLGlOMiElZHGPdIi3hWolJEwKgV6YKceOuTaPhBXGdXJS", - "iAURZSZysNXZ0hTUN1fFOUXZCyJXtiQKLO1gygDbJ6LjgVOa29eaZycor+2t/uE3/8L0sekr6px8dtET", - "6yLQE18AymWFcxiyjbvw2vxxmJGprZRNi8hztkG6AZk68nOiZQVj4lpYgSQmITz4VAJZMaUsKIGWrllR", - "YPYItokcGoI/UBq1PbLzGfpBrxk6vDUziViRujS3Y0i/EvOA8zgjG9FLKarFMipaEOD0T3dZuYd9PMrP", - "qkKfRAwRNVM8ISuhtHsW25HqJdcuoJ9lgmspiqKpyLNy/sIZfV/SzWmW6RdCXM1odvUQH+Fc6LDSfOxT", - "KrR9d+uZZCsx5LCXgt7wCZKH2p/73bZDr1ZHz4N5Z4v7dQwP+zT5EZjv9jPX/XaN0+7C2utq8tn0W+iU", - "E6rFimXp4/bn8n7t9VlNca9kpkVbGtlmocFmyAfieyy4MyH37KIZOE3Wdj0ljkc4tw7kROa/KMa3xyVz", - "cDyo5w7t8h0nYE2yXjGwBQBCahMh6EraesqxkBYYjljYxCnolNIGdOCFg75/d4PNjHDvQGm4E1Adb+QA", - "4GdWgzG2GTGtZ/NMbPz3h3XKzFsBf7ObyhvMo8+p8rwmLWndKn0iqx6OkK6KsNMD8QKTYMyG+iGG+vgD", - "L/8IgH7PxAYMg/wTDwVjTlkB+SRVOvks6MDG0XPdxVhGo/sik5aTZ7Ty5YnN2JUEl1jJSv+yaU4sqSEl", - "EZp3NeI8hw3YGK3fQQpbXHgcmbOgsLWHWxoFUU4KWEPDYdNle6pQCmVr8H1V6ExygBItvm1FW8oTMS5d", - "2NK+uLVPIl+2IdhNqmMsYu1OkT26lqRmaMMn9piooUfJQLRmeUUb+FOHihxNXaI5yglUdZ4PE//EHDrN", - "z3aEN36AU98/Jcp4TLwbxocOZkFp1O1iQHs9kyvVd+p52jE5TmUWDEU4Wx7s2pbEa76hSnrN+7WaXZKv", - "X2ID94kJHiH22w1kKNW4pxDk7jHUYzlxOZCQ2jlAbh8MpktCm78ETriICjFfUxVeMXVWV/+DnRgbMe4e", - "2rew0df+w3ffWYKDEdVKtpiumxrI+m46/k9yEncexN7xUjSiwIXy7lCNeep2zw5sIKoiJ9zsp5H9sXCx", - "u8UcFx+TWeUHKgpxbSsrx0/U5+DtuZb6vInJieUsXMveT3rsEg63tSAsihBZ0S0REv8xD9J/VLRg8y3y", - "GQu+70bUkhoScgZk60Xh/K7NxLvFq7EHzCtihJ/KrpsNHTMabmtGiYA2F7mvJSfIil5BvA3oIGL5Z6YN", - "41TVDJUa5spubWcXC27xPj3TiuaxEgATzW4b3MEnPDe9//86bDWeyud/LAua+TrariJek89gqX1PXHoJ", - "q91hzl2+5kkglO+viVb6NBn5LbSpB7KuVMxPX8WuBtiduuSdYmV3WsZApXCr8NKOAPFBS7nvXbifGM7O", - "kuL6w/sWF5dj/ji7k8wQ3beMIeD/gXal4V7RiWxLl3WP12MruH+EXWgk4knAatXgM7GZSJirfY40Vg8+", - "E5saYBV0t4xnEqiyfkdnr9yztU6AzLh5Rluv3WBWDaPkMGe8ZrWMl5VOvIIwDzLfRgiLrQmI1h7bXJ+M", - "YUTRNS1erUFKlvdtnDk9tl5xXDnIW1Bc34QCJNzI3QGYql+AGE9d6+fjZub6t1UPre+s0pTnVOZxc8ZJ", - "BtJIDeSabtXtTVXB6rDPWEUjWaiZLSQyWyFpW0CKrbM239GQFACk92hRGmAJQifthBXIKoa06DH8dGH4", - "U1iCVnQzKcQCo357DoTLc42mQ/uAFByV6Fa6G7ZuP49iv8PuabAUiWNEWuCsQ6bYfe5f4VbiI/RnzvTO", - "k281nO0wbOvpbA+mRypf1OEZlli65zEVOe8SM8XR815U9WlKPO1BtIlJl+iOVr1nF9G/wqVdiFXowyto", - "Nl04UvH5Vq8wQX2D2hGAAaqOK6CZ8xDrKuI6igqLlLHLbnCgns5q9/291AMeKlKUO+vNaYODjhnnkLKj", - "u/MZTEpRTrIhvq22WlHujAwO0iaMPfQRmRB61h38blSo39XIidYo5HVo5dXeQmL7bGVltktl0Kdk6uHo", - "TQOGmCMvwyNsVWsYaxVUMWP/OPfG7qYSLTAJQomErJKoZL6m2/3VKHuyz5//cPrFo8e/Pv7iS2IakJwt", - "QNU1DVrVHGvXRMbbWqOP64zYWZ5Ob4LPFmIR562XPuwtbIo7a5bbqjoZcaeW5SHa6cQFkArO7ZbIu9Ve", - "4Th1WMQfa7tSi7z3HUuh4MPvmRRFka4pE+SqhPkltVuRAca8QEqQiiltGGHTfsp07ZStlqhcxKzha5sb", - "SvAMvPbZUQHTPb5cqYX0+fQiP8NcDM7mRGBTFo5XWTvRrnW5d5rV76HQiO42MyClKJ1oz+YkBRHGbMkK", - "gl7dqU1Rnx656QZmax12U4TonN/TpHfK3UtYzMlubt+sD67TnN5sYkK88IfyFqTZZ93ozzNyG05SGwb+", - "MPwjkTjl3rhGWO6H4BXJ98GOqPDTjtdESBoyCLRugowEeSAAPfHQjaDVKMguyk0urY0BrRHe/NwWP17W", - "Zum9kSkIie+wB7w4lrluF4IpHDifOLH3y4CUaCnv+iihsfx94dGe9YaLJNoipzTRGpRlS6IrFkYB8eqb", - "EGfe8yrphKNLITQxL9OiSISxWz0OnqmYcMyTQK5p8fG5xndMKn2K+ID8TX/gVhy2HCPZolLde0LOF3QQ", - "WFGI8keBir/G2Pr/ALOzydvRzeIM/507EFVCtLDe3vNgAQdOrnFM69j16Esyc+V+SgkZU22Hgmsv0oR4", - "W5Bs7vxrYaPbsb93LhP0i9B3OA5z7w9EfoqMbMFzwMFcH/VPzJx6OEDytKRItUMoCfyleF1c6X3PtXPH", - "0jC3S+UUJW48MJVTt4b90OXhOvDyqhR01zn41m/gNnHh12sbmqtscIWZy8u3ejYkoVi6GozpjjnO7qUs", - "zN2LwnyUBGcWlW4MB0mSsGqRe1/2mpa/ZJSnobmLRtzvKSC/tOg3o+GjYF5xO14ogIqx4p6ti/k4eDEI", - "bro9JZf8iKgl9W8L9+fjL74cjUfAq5VZfP19NB65r+9SL7V8k4wrrRPpdHxEXTWBB4qUdDskmH1v6pwk", - "futMQR9fpFGazdJvuh/MnuHD1QUgnHFk9che7A3q8uf8lQBoJzG0Dms4MZYk6/RAYSv2ZQr6pS8tvk39", - "3lPto8V9K1bsdZJrFGK5GY8WNkkZVif51dWq+7jb7iHoyRfoln6XNGAWMYm1NiaPpoqSug0oyOK6JSpk", - "YOR1Vkmmt+cG/17tzn69SiWD+j6kZ3I5v4IF3sm+WlwB9z5mdTKnSnnp+ntBC5Q+rWMANzKnKKbkW1sh", - "xF2LXz+Y/St8/rcn+cnnj/519reTL04yePLFVycn9Ksn9NFXnz+Cx3/74skJPJp/+dXscf74yePZk8dP", - "vvziq+zzJ49mT7786l8fGEo3IFtAfeWfp6P/nJwWCzE5fX02uTDA1jihJfsRzN6ghm2OCQoRqRlesbCi", - "rBg99T/9L39RTjOxqof3v45cPcjRUutSPT0+vr6+nsZdjheYA2WiRZUtj/08mMuy8V55fRbigqzvH+5o", - "bXPCTQ35/cy3N9+eX5DT12fTmmBGT0cn05PpI8ynWAKnJRs9HX2OP+HpWeK+H2MW7WPlivEch9DRm3Hn", - "W1naUj3m0yKkATV/LYEWyCLNHyvQkmX+kwSab93/1TVdLEBOMWLM/rR+fOzfHsfvXV6Zm13fjmNvtOP3", - "jeQ8+Z6e3p9qX5Pj977c/+4BG6XcnZ+rQWrSUeJ70C6NntVYJvIcoX3SjT4mCuvumJ9KyYQ5r2MjLuSA", - "3kToUiuxvoiWFc+si4mdAjj+9+Xpf6KbzcvT/yRfk5OxC3JSqAZJTW9TRQRCO8st2F3faPVsexoyQdUu", - "OaOnb1OqaeegXlazgmXmFTH1J9WQYXSQwpA1o0RDxMheFOgfENi+YeUnk6/evf/ibzdJj/Suc1rt1bnz", - "a0fCDPiNMic13EyEL+SO+F7Rzdd92N64gBkz7j8qkNt6+Stqbul6qQMlwOSviZSZPvLx2tVMj52uI3fs", - "fz9/9RMRkjjF3muaXYWoTx8BXEc9xwHApmff2tztHS/Py9UufHSlFmWzlkDQA7zDotAIKPKsxycnnlE7", - "pUfEBY4dc4lmaonGXWpGD8LIhtJN/6EIbGimiy2hKnLhQndsX8C9FZsrykkjQmin1aY7o9uSZHjVoRlI", - "EqVwhKbFHvguWsWuG+hw75bS3OT7U350kJGEIJnuNt5aTyN/7e7/jN3tij6kFOZMMww4qe81f2c2gHQC", - "b7H14PakYZqS/xIVCqjm6VFpCCxQSGRn4Va25lo3p8tCF73S6phI/HJ01F740VHtkTyHa2SylGPDNjqO", - "jqZmp54cyMp2mtAaFQkGnZ1Dhuts1ku6CYoUSrjgEw4LqtkaSORN9eTk0Z92hWfcBuAYidy+HG7Goy/+", - "xFt2xo3wRAuCLe1qPv/TruYc5JplQC5gVQpJJSu25GceIpzsywrlky77+5lfcXHNPSLMo7harajcOkmd", - "Bp5T8agU4U7+08n/VkvzyEXpQqGbHsrBVnD2eW/5YvTuxj80Bj5wdjU7nmG97qFNIX4V9T+BUEupjt+j", - "ba/392On50t/RPOrfaEfe+VlT0ubgzD9sfH0eq83ZiG7hzNtovEyqrNlVR6/x//gYztaka3/c6w3/Bjd", - "1Y/fNxDhPncQ0fy97h63wLIVHjgxnyt8LO76fPze/htNBJsSJDPXEaZGdr/abPjHqirLYtv9ecuz5I/d", - "dTSSfvf8fOx1Pal3e7Pl+8afTZpSy0rn4jqaBa2k1jGgC5n5WKn238fXlGkjJLms0XSuQXY7a6DFsatM", - "2Pq1LvfT+YI1jKIfW2JVKWzitOaz+Q29vmgEuUubBeiZQG1IH8PdTGaMIxeKuWSt+7Qfu0+kDm/E+vFb", - "XTudJGRQLchMCppnVGnzR11mpPn+vrnj+6udtOgs4TeAYKJOo2sjMfxkutc+jOMOETKjfSFnz/2EdWTt", - "BxfMOhA9oznxmfYm5CUtzIZDTk6d+N/AxocWqj69FPSJxZaPJmc884dPEYppRxsPRJnOBhbV4R0iVJhX", - "pGEAC+ATx4ImM5FvXcXTkaTXemOTD7WZ2zFt3hhNbSeVdKX6Pt6DKvSPrf/cp/b8S2X4l8rwL6XSXyrD", - "v3b3L5XhvasM/1Ko/aVQ+39SoXaIFi0lkDpFUb9cytbAbdhN64VI61JDgcU3EygyHaS3Riw8VjViekrI", - "BWa/ouaWgDVIWpCMKitduUxtK3Q2xzSMkD+95JMGJNaB20z8Wf1f62Z/WZ2cfA7k5GG7j9KsKGLe3O2L", - "kjF+skFyX5PL0eWoM5KElVhDbr3U4joTttfeYf+/MO6rTkEbTOWB6cV8tkaiqvmcZcyivBB8QehC1NEj", - "mK6aC/wC0gBnfRsJ02MXbcdcige7K61yGE0ZvysBnNVbuNfDoUUuad8GQ3gHOjb8S0rl8ZeUPpDZ7Uro", - "d1dGunPsDlf9i6t8DK7yyfnKn92cGykh/0eKmU9OnvxpFxSrrH8SmnznY9/uII65XMlZsuDibQUtn+3K", - "KwZrP+nY7xhv0eBx/PaduQgUyLW/YGs32qfHx5g8cSmUPkYlVdPFNv74LsD83t9OpWRrA80N6kGFZAvG", - "aTFxfqiT2lX28fRkdPN/AwAA//9KiHz6MCcBAA==", + "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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 348c973461..437aa136c2 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -27,6 +27,7 @@ import ( "net/http" "os" "runtime" + "slices" "strconv" "strings" "sync/atomic" @@ -82,6 +83,17 @@ const MaxAssetResults = 1000 // /v2/accounts/{address}/assets endpoint const DefaultAssetResults = uint64(1000) +// MaxApplicationResults sets a size limit for the number of applications returned in a single request to the +// /v2/accounts/{address}/applications endpoint when includeParams=true +const MaxApplicationResults = 1000 + +// MaxApplicationResultsWithoutParams sets a higher limit when params are excluded, since responses are ~100x smaller +const MaxApplicationResultsWithoutParams = MaxApplicationResults * 100 + +// DefaultApplicationResults sets a default size limit for the number of applications returned in a single request to the +// /v2/accounts/{address}/applications endpoint +const DefaultApplicationResults = uint64(1000) + const ( errInvalidLimit = "limit parameter must be a positive integer" errUnableToParseNext = "unable to parse next token" @@ -111,8 +123,10 @@ type LedgerForAPI interface { 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) + LookupApplications(addr basics.Address, appIDGT basics.AppIndex, limit uint64, includeParams bool) ([]ledgercore.AppResourceWithIDs, basics.Round, error) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreement.Certificate, err error) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) + OnlineCirculation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) Wait(r basics.Round) chan struct{} WaitWithCancel(r basics.Round) (chan struct{}, func()) @@ -416,13 +430,36 @@ func (v2 *Handlers) AccountInformation(ctx echo.Context, address basics.Address, } // should we skip fetching apps and assets? - if params.Exclude != nil { - switch *params.Exclude { - case "all": - return v2.basicAccountInformation(ctx, address, handle, contentType) - case "none", "": - default: - return badRequest(ctx, err, errFailedToParseExclude, v2.Log) + var excludeCreatedAppsParams bool + var excludeCreatedAssetsParams bool + if params.Exclude != nil && len(*params.Exclude) > 0 { + excludeValues := *params.Exclude + + // Handle special cases that must be alone + if len(excludeValues) == 1 { + val := strings.TrimSpace(string(excludeValues[0])) + if val == "all" { + return v2.basicAccountInformation(ctx, address, handle, contentType) + } + if val == "none" || val == "" { + // Default behavior - include everything + excludeValues = nil + } + } + + // Parse exclusions + for _, excludeVal := range excludeValues { + val := strings.TrimSpace(string(excludeVal)) + switch val { + case "created-apps-params": + excludeCreatedAppsParams = true + case "created-assets-params": + excludeCreatedAssetsParams = true + case "all", "none": + return badRequest(ctx, fmt.Errorf("'%s' cannot be combined with other exclude values", val), errFailedToParseExclude, v2.Log) + default: + return badRequest(ctx, fmt.Errorf("invalid exclude value: %s", val), errFailedToParseExclude, v2.Log) + } } } @@ -470,7 +507,10 @@ func (v2 *Handlers) AccountInformation(ctx echo.Context, address basics.Address, return internalError(ctx, err, fmt.Sprintf("could not retrieve consensus information for last round (%d)", lastRound), v2.Log) } - account, err := AccountDataToAccount(address.String(), &record, lastRound, &consensus, amountWithoutPendingRewards) + account, err := AccountDataToAccount(address.String(), &record, lastRound, &consensus, amountWithoutPendingRewards, AccountDataToAccountOptions{ + ExcludeCreatedAppsParams: excludeCreatedAppsParams, + ExcludeCreatedAssetsParams: excludeCreatedAssetsParams, + }) if err != nil { return internalError(ctx, err, errInternalFailure, v2.Log) } @@ -586,7 +626,7 @@ func (v2 *Handlers) AccountAssetInformation(ctx echo.Context, address basics.Add if record.AssetParams != nil { asset := AssetParamsToAsset(address.String(), assetID, record.AssetParams) - response.CreatedAsset = &asset.Params + response.CreatedAsset = asset.Params } if record.AssetHolding != nil { @@ -634,7 +674,7 @@ func (v2 *Handlers) AccountApplicationInformation(ctx echo.Context, address basi if record.AppParams != nil { app := AppParamsToApplication(address.String(), applicationID, record.AppParams) - response.CreatedApp = &app.Params + response.CreatedApp = app.Params } if record.AppLocalState != nil { @@ -939,7 +979,20 @@ func (v2 *Handlers) GetTransactionProof(ctx echo.Context, round basics.Round, tx func (v2 *Handlers) GetSupply(ctx echo.Context) error { latest, totals, err := v2.Node.LedgerForAPI().LatestTotals() if err != nil { - err = fmt.Errorf("GetSupply(): round %d, failed: %v", latest, err) + err = fmt.Errorf("GetSupply(): round %d, LatestTotals failed: %v", latest, err) + return internalError(ctx, err, errInternalFailure, v2.Log) + } + + params, err := v2.Node.LedgerForAPI().ConsensusParams(latest) + if err != nil { + err = fmt.Errorf("GetSupply(): round %d, ConsensusParams failed: %v", latest, err) + return internalError(ctx, err, errInternalFailure, v2.Log) + } + + brnd := agreement.BalanceRound(latest, params) + onlineCirculation, err := v2.Node.LedgerForAPI().OnlineCirculation(brnd, latest) + if err != nil { + err = fmt.Errorf("GetSupply(): round %d, OnlineCirculation failed: %v", latest, err) return internalError(ctx, err, errInternalFailure, v2.Log) } @@ -947,6 +1000,7 @@ func (v2 *Handlers) GetSupply(ctx echo.Context) error { CurrentRound: latest, TotalMoney: totals.Participating().Raw, OnlineMoney: totals.Online.Money.Raw, + OnlineStake: onlineCirculation.Raw, } return ctx.JSON(http.StatusOK, supply) @@ -1136,10 +1190,6 @@ func (v2 *Handlers) RawTransactionAsync(ctx echo.Context) error { // AccountAssetsInformation looks up an account's asset holdings. // (GET /v2/accounts/{address}/assets) func (v2 *Handlers) AccountAssetsInformation(ctx echo.Context, address basics.Address, 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") - } - var assetGreaterThan uint64 = 0 if params.Next != nil { agt, err0 := strconv.ParseUint(*params.Next, 10, 64) @@ -1207,7 +1257,7 @@ func (v2 *Handlers) AccountAssetsInformation(ctx echo.Context, address basics.Ad if !record.Creator.IsZero() { asset := AssetParamsToAsset(record.Creator.String(), record.AssetID, record.AssetParams) - aah.AssetParams = &asset.Params + aah.AssetParams = asset.Params } assetHoldings = append(assetHoldings, aah) @@ -1218,6 +1268,110 @@ func (v2 *Handlers) AccountAssetsInformation(ctx echo.Context, address basics.Ad return ctx.JSON(http.StatusOK, response) } +// AccountApplicationsInformation returns application resources for a specific address. +func (v2 *Handlers) AccountApplicationsInformation(ctx echo.Context, address basics.Address, params model.AccountApplicationsInformationParams) error { + var appGreaterThan 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) + } + appGreaterThan = agt + } + + // Determine includeParams first, as it affects limit validation + includeParams := false + if params.Include != nil { + if slices.Contains(*params.Include, model.AccountApplicationsInformationParamsIncludeParams) { + includeParams = true + } + } + + // Choose appropriate max limit based on whether params are included + // When params are excluded, responses are ~100x smaller, so we can return more results + maxLimit := uint64(MaxApplicationResults) + if !includeParams { + maxLimit = uint64(MaxApplicationResultsWithoutParams) + } + + if params.Limit != nil { + if *params.Limit <= 0 { + return badRequest(ctx, errors.New(errInvalidLimit), errInvalidLimit, v2.Log) + } + + if *params.Limit > maxLimit { + limitErrMsg := fmt.Sprintf("limit %d exceeds max applications single batch limit %d", *params.Limit, maxLimit) + return badRequest(ctx, errors.New(limitErrMsg), limitErrMsg, v2.Log) + } + } else { + // default limit + l := DefaultApplicationResults + params.Limit = &l + } + + ledger := v2.Node.LedgerForAPI() + + // Logic + // 1. Get the account's application resources 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 applications. + records, lookupRound, err := ledger.LookupApplications(address, basics.AppIndex(appGreaterThan), *params.Limit+1, includeParams) + + if err != nil { + return internalError(ctx, err, errFailedLookingUpLedger, v2.Log) + } + + // prepare JSON response + response := model.AccountApplicationsInformationResponse{Round: lookupRound} + + // If the total count is greater than the limit, we set the next token to the last application 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].AppID), 10) + response.NextToken = &nextTk + } + + applicationResources := make([]model.AccountApplicationResource, 0, len(records)) + + for _, record := range records { + // Skip if neither local state nor params exist (shouldn't happen) + if record.AppLocalState == nil && record.AppParams == nil { + v2.Log.Warnf("AccountApplicationsInformation: application %d has neither local state nor params", record.AppID) + continue + } + + aah := model.AccountApplicationResource{ + Id: record.AppID, + } + + if !record.Creator.IsZero() { + // App exists - don't set Deleted field (omit from JSON) + if includeParams && record.AppParams != nil { + app := AppParamsToApplication(record.Creator.String(), record.AppID, record.AppParams) + aah.Params = app.Params + } + } else { + // App deleted - set Deleted=true (only include when true) + deleted := true + aah.Deleted = &deleted + } + + if record.AppLocalState != nil { + appLocalState := AppLocalState(*record.AppLocalState, record.AppID) + aah.AppLocalState = &appLocalState + } + + applicationResources = append(applicationResources, aah) + } + + response.ApplicationResources = &applicationResources + + 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/handlers_test.go b/daemon/algod/api/server/v2/handlers_test.go index 03186a7cef..fbf564407f 100644 --- a/daemon/algod/api/server/v2/handlers_test.go +++ b/daemon/algod/api/server/v2/handlers_test.go @@ -23,10 +23,11 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" + "github.com/algorand/go-algorand/test/partitiontest" ) func TestApplicationBoxesMaxKeys(t *testing.T) { diff --git a/daemon/algod/api/server/v2/test/genesis_types_test.go b/daemon/algod/api/server/v2/test/genesis_types_test.go index 3795a2b9b0..526396074c 100644 --- a/daemon/algod/api/server/v2/test/genesis_types_test.go +++ b/daemon/algod/api/server/v2/test/genesis_types_test.go @@ -21,10 +21,11 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // getCodecTag extracts the base name from a codec tag, ignoring any additional parameters 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 d4fe5daa70..6753001620 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -21,11 +21,10 @@ import ( "fmt" "net/http" "net/http/httptest" + "slices" "strconv" "testing" - "github.com/algorand/go-algorand/data/transactions/logic" - "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -38,11 +37,13 @@ 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/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" + "github.com/algorand/go-algorand/util" ) type mockLedger struct { @@ -148,12 +149,71 @@ func (l *mockLedger) LookupApplication(rnd basics.Round, addr basics.Address, ai } return ar, nil } + +func (l *mockLedger) LookupApplications(addr basics.Address, appIDGT basics.AppIndex, limit uint64, includeParams bool) ([]ledgercore.AppResourceWithIDs, basics.Round, error) { + ad, ok := l.accounts[addr] + if !ok { + return nil, basics.Round(0), nil + } + + // Collect all app IDs owned by this account (both created and opted-in) + appIDSet := make(util.Set[basics.AppIndex]) + for appID := range ad.AppParams { + if appID > appIDGT { + appIDSet.Add(appID) + } + } + for appID := range ad.AppLocalStates { + if appID > appIDGT { + appIDSet.Add(appID) + } + } + + // Sort app IDs to match database ORDER BY behavior + var ownedAppIDs []basics.AppIndex + for appID := range appIDSet { + ownedAppIDs = append(ownedAppIDs, appID) + } + slices.Sort(ownedAppIDs) + + // Return up to 'limit' apps (matching database LIMIT behavior) + if uint64(len(ownedAppIDs)) > limit { + ownedAppIDs = ownedAppIDs[:limit] + } + + // Build results for the selected apps + var res []ledgercore.AppResourceWithIDs + for _, appID := range ownedAppIDs { + apr := ledgercore.AppResourceWithIDs{ + AppID: appID, + } + + if ap, ok := ad.AppParams[appID]; ok { + // Only populate AppParams if requested to match the optimization in the real implementation + if includeParams { + apr.AppParams = &ap + } + apr.Creator = addr + } + + if ls, ok := ad.AppLocalStates[appID]; ok { + apr.AppLocalState = &ls + } + + res = append(res, apr) + } + return res, basics.Round(0), nil +} + func (l *mockLedger) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreement.Certificate, err error) { panic("not implemented") } func (l *mockLedger) LatestTotals() (rnd basics.Round, at ledgercore.AccountTotals, err error) { panic("not implemented") } +func (l *mockLedger) OnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) { + panic("not implemented") +} func (l *mockLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { blk, err := l.Block(rnd) if err != nil { @@ -277,7 +337,6 @@ 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, @@ -299,7 +358,8 @@ func accountInformationResourceLimitsTest(t *testing.T, accountMaker func(int) b handlers, addr, acctData := setupTestForLargeResources(t, acctSize, maxResults, accountMaker) params := model.AccountInformationParams{} if exclude != "" { - params.Exclude = (*model.AccountInformationParamsExclude)(&exclude) + excludeSlice := []model.AccountInformationParamsExclude{model.AccountInformationParamsExclude(exclude)} + params.Exclude = &excludeSlice } ctx, rec := newReq(t) err := handlers.AccountInformation(ctx, addr, params) @@ -381,7 +441,7 @@ func accountInformationResourceLimitsTest(t *testing.T, accountMaker func(int) b assert.Nil(t, ret.AssetHolding) ap := acctData.AssetParams[aidx] assetParams := v2.AssetParamsToAsset(addr.String(), aidx, &ap) - assert.Equal(t, ret.CreatedAsset, &assetParams.Params) + assert.Equal(t, ret.CreatedAsset, assetParams.Params) } for i := 0; i < ret.TotalApps; i++ { ctx, rec = newReq(t) @@ -545,3 +605,238 @@ func TestAccountInformationResourceLimits(t *testing.T) { }) } } + +func randomAccountWithSomeAppLocalStatesAndOverlappingAppParams(overlapN int, nonOverlapAppLocalStatesN int) basics.AccountData { + a := ledgertesting.RandomAccountData(0) + a.AppParams = make(map[basics.AppIndex]basics.AppParams) + a.AppLocalStates = make(map[basics.AppIndex]basics.AppLocalState) + + // Use sparse app IDs to test pagination with non-sequential IDs + // This exercises the fix for https://github.com/algorand/go-algorand/pull/6552#discussion_r2795538306 + const appIDGap = 100 // Create 100-ID gaps between apps + + // overlapN apps have both app params and app local states + for i := 1; i <= overlapN; i++ { + appID := basics.AppIndex(i * appIDGap) + a.AppParams[appID] = ledgertesting.RandomAppParams() + a.AppLocalStates[appID] = ledgertesting.RandomAppLocalState() + } + + // nonOverlapAppLocalStatesN apps have only app local states + for i := 1; i <= nonOverlapAppLocalStatesN; i++ { + appID := basics.AppIndex((overlapN + i) * appIDGap) + a.AppLocalStates[appID] = ledgertesting.RandomAppLocalState() + } + return a +} + +func accountApplicationInformationResourceLimitsTest(t *testing.T, handlers v2.Handlers, addr basics.Address, + acctData basics.AccountData, params model.AccountApplicationsInformationParams, inputNextToken int, maxResults int, expectToken bool) { + + ctx, rec := newReq(t) + err := handlers.AccountApplicationsInformation(ctx, addr, params) + require.NoError(t, err) + require.Equal(t, 200, rec.Code) + var ret model.AccountApplicationsInformationResponse + 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 app id returned + assert.EqualValues(t, (*ret.ApplicationResources)[maxResults-1].Id, nextRaw) + } + assert.Equal(t, maxResults, len(*ret.ApplicationResources)) + + // Build sorted list of expected app IDs from account data (handles sparse IDs) + var expectedAppIDs []basics.AppIndex + appIDSet := make(util.Set[basics.AppIndex]) + for appID := range acctData.AppParams { + if appID > basics.AppIndex(inputNextToken) { + appIDSet.Add(appID) + } + } + for appID := range acctData.AppLocalStates { + if appID > basics.AppIndex(inputNextToken) { + appIDSet.Add(appID) + } + } + for appID := range appIDSet { + expectedAppIDs = append(expectedAppIDs, appID) + } + slices.Sort(expectedAppIDs) + + // Verify returned apps match expected IDs + for i := 0; i < maxResults && i < len(expectedAppIDs); i++ { + expectedID := expectedAppIDs[i] + assert.Equal(t, expectedID, (*ret.ApplicationResources)[i].Id, "App at position %d should have ID %d", i, expectedID) + if (*ret.ApplicationResources)[i].AppLocalState != nil { + assert.Equal(t, expectedID, (*ret.ApplicationResources)[i].AppLocalState.Id) + } + } +} + +// TestAccountApplicationsInformation tests the account application information endpoint +func TestAccountApplicationsInformation(t *testing.T) { + partitiontest.PartitionTest(t) + + accountOverlappingAppParamsLocalStatesCount := 1000 + accountNonOverlappingAppLocalStatesCount := 25 + totalAppLocalStates := accountOverlappingAppParamsLocalStatesCount + accountNonOverlappingAppLocalStatesCount + + handlers, addr, acctData := setupTestForLargeResources(t, accountOverlappingAppParamsLocalStatesCount, 50, func(N int) basics.AccountData { + return randomAccountWithSomeAppLocalStatesAndOverlappingAppParams(N, accountNonOverlappingAppLocalStatesCount) + }) + + // 1. Query with no limit/pagination - should get DefaultApplicationResults back + accountApplicationInformationResourceLimitsTest(t, handlers, addr, acctData, model.AccountApplicationsInformationParams{}, + 0, int(v2.DefaultApplicationResults), false) + + rawLimit := 100 + limit := uint64(rawLimit) + // 2. Query with limit P/2 { @@ -293,6 +324,12 @@ func testingenvWithBalances(t testing.TB, minMoneyAtStart, maxMoneyAtStart, numA data := basics_testing.MakeAccountData(basics.Online, startamt) data.SelectionID = parts[i].VRFSecrets().PK data.VoteID = parts[i].VotingSecrets().OneTimeSignatureVerifier + if numExpiredOnline > 0 && i < numExpiredOnline { + if expiredVoteLastValid == 0 { + expiredVoteLastValid = 50 // default if zero + } + data.VoteLastValid = basics.Round(expiredVoteLastValid) + } genesis[short] = data } part.Close() diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index b2757e375d..2d26d401ac 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -27,9 +27,10 @@ import ( "unicode" "unicode/utf8" - "github.com/algorand/go-codec/codec" "github.com/labstack/echo/v4" + "github.com/algorand/go-codec/codec" + "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" diff --git a/daemon/algod/server_test.go b/daemon/algod/server_test.go index 53ec159b7c..c5ceaa84dc 100644 --- a/daemon/algod/server_test.go +++ b/daemon/algod/server_test.go @@ -23,8 +23,9 @@ import ( "net" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func isTCPPortAvailable(host string, port int) bool { diff --git a/daemon/kmd/server/server.go b/daemon/kmd/server/server.go index 1c441a9c71..e2cec6c8dc 100644 --- a/daemon/kmd/server/server.go +++ b/daemon/kmd/server/server.go @@ -26,9 +26,10 @@ import ( "syscall" "time" - "github.com/algorand/go-deadlock" "github.com/gofrs/flock" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/daemon/kmd/api" "github.com/algorand/go-algorand/daemon/kmd/session" "github.com/algorand/go-algorand/logging" diff --git a/daemon/kmd/session/session.go b/daemon/kmd/session/session.go index 3e6580228d..8d9009769b 100644 --- a/daemon/kmd/session/session.go +++ b/daemon/kmd/session/session.go @@ -20,9 +20,10 @@ import ( "context" "time" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/daemon/kmd/config" "github.com/algorand/go-algorand/daemon/kmd/wallet" - "github.com/algorand/go-deadlock" ) type walletHandle struct { diff --git a/daemon/kmd/wallet/driver/ledger_hid.go b/daemon/kmd/wallet/driver/ledger_hid.go index 32f5e96c3a..a89fa68d32 100644 --- a/daemon/kmd/wallet/driver/ledger_hid.go +++ b/daemon/kmd/wallet/driver/ledger_hid.go @@ -169,6 +169,11 @@ func (l *LedgerUSB) ReadPackets() ([]byte, error) { // Exchange sends a message to the Ledger device, waits for a response, // and returns the response data. func (l *LedgerUSB) Exchange(msg []byte) ([]byte, error) { + // Mask SIGURG to prevent Go's async preemption from interrupting + // macOS IOKit HID calls (causes kIOReturnError on Darwin). + cleanup := maskSIGURG() + defer cleanup() + err := l.WritePackets(msg) if err != nil { return nil, err diff --git a/daemon/kmd/wallet/driver/ledger_hid_darwin.go b/daemon/kmd/wallet/driver/ledger_hid_darwin.go new file mode 100644 index 0000000000..55aa2b9438 --- /dev/null +++ b/daemon/kmd/wallet/driver/ledger_hid_darwin.go @@ -0,0 +1,56 @@ +// Copyright (C) 2019-2026 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 driver + +import ( + "runtime" +) + +// #include +import "C" + +// maskSIGURG temporarily blocks SIGURG signals during HID operations on macOS. +// +// Go's runtime uses SIGURG for async preemption (goroutine scheduling and GC), +// but this can interfere with macOS IOKit HID calls. When a SIGURG signal is +// delivered during an IOHIDDeviceSetReport call, macOS may return +// kIOReturnError (0xE00002BC), causing the HID operation to fail. +// +// By intercepting SIGURG signals during HID I/O, we prevent the Go runtime's +// async preemption from interrupting the underlying IOKit calls. +// +// Returns a cleanup function that must be called to restore normal signal +// handling. Typical usage: +// +// cleanup := maskSIGURG() +// defer cleanup() +// // ... perform HID operations ... +func maskSIGURG() func() { + runtime.LockOSThread() + + var oldset, newset C.sigset_t + C.sigemptyset(&newset) + C.sigaddset(&newset, C.SIGURG) + C.pthread_sigmask(C.SIG_BLOCK, &newset, &oldset) + + return func() { + C.pthread_sigmask(C.SIG_SETMASK, &oldset, nil) + runtime.UnlockOSThread() + } +} diff --git a/daemon/kmd/wallet/driver/ledger_hid_other.go b/daemon/kmd/wallet/driver/ledger_hid_other.go new file mode 100644 index 0000000000..86e4b1796c --- /dev/null +++ b/daemon/kmd/wallet/driver/ledger_hid_other.go @@ -0,0 +1,28 @@ +// Copyright (C) 2019-2026 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 driver + +// maskSIGURG is a no-op on non-Darwin platforms. +// +// On macOS, Go's SIGURG-based async preemption can interfere with IOKit HID +// calls, but this is not an issue on other platforms. See ledger_hid_darwin.go +// for the Darwin-specific implementation. +func maskSIGURG() func() { + return func() {} +} diff --git a/daemon/kmd/wallet/driver/sqlite.go b/daemon/kmd/wallet/driver/sqlite.go index 67099647a3..77b4ce9335 100644 --- a/daemon/kmd/wallet/driver/sqlite.go +++ b/daemon/kmd/wallet/driver/sqlite.go @@ -24,10 +24,12 @@ import ( "path/filepath" "regexp" - "github.com/algorand/go-deadlock" "github.com/jmoiron/sqlx" "github.com/mattn/go-sqlite3" + "github.com/algorand/go-codec/codec" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/daemon/kmd/config" "github.com/algorand/go-algorand/daemon/kmd/wallet" @@ -35,7 +37,6 @@ import ( "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-codec/codec" ) const ( diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index cbd4de2486..89f137f9d2 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -24,6 +24,8 @@ import ( "fmt" "time" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -31,7 +33,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" - "github.com/algorand/go-deadlock" ) const defaultTimeout = 5 * time.Second diff --git a/data/appRateLimiter.go b/data/appRateLimiter.go index 230fa8714a..b95b22798a 100644 --- a/data/appRateLimiter.go +++ b/data/appRateLimiter.go @@ -22,14 +22,16 @@ import ( "sync/atomic" "time" + "golang.org/x/crypto/blake2b" + + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config/bounds" "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 diff --git a/data/appRateLimiter_test.go b/data/appRateLimiter_test.go index 690fcb2012..dc4080053b 100644 --- a/data/appRateLimiter_test.go +++ b/data/appRateLimiter_test.go @@ -21,6 +21,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/blake2b" + "golang.org/x/exp/rand" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/config/bounds" "github.com/algorand/go-algorand/crypto" @@ -28,9 +32,6 @@ import ( "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) { diff --git a/data/basics/fraction_test.go b/data/basics/fraction_test.go index 02f61b8a30..a73744a297 100644 --- a/data/basics/fraction_test.go +++ b/data/basics/fraction_test.go @@ -20,8 +20,9 @@ import ( "math" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestFraction(t *testing.T) { diff --git a/data/basics/serr_test.go b/data/basics/serr_test.go index 90f8a9b6d6..685c0690ec 100644 --- a/data/basics/serr_test.go +++ b/data/basics/serr_test.go @@ -21,8 +21,9 @@ import ( "fmt" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestNew(t *testing.T) { diff --git a/data/basics/testing/nearzero_test.go b/data/basics/testing/nearzero_test.go index ef47b13096..e2363e779d 100644 --- a/data/basics/testing/nearzero_test.go +++ b/data/basics/testing/nearzero_test.go @@ -19,8 +19,9 @@ package testing import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestNearZeros(t *testing.T) { diff --git a/data/basics/units_test.go b/data/basics/units_test.go index 8bd3ee6667..8d259922d0 100644 --- a/data/basics/units_test.go +++ b/data/basics/units_test.go @@ -21,9 +21,10 @@ import ( "math/big" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestODiff(t *testing.T) { diff --git a/data/bookkeeping/lightBlockHeader_test.go b/data/bookkeeping/lightBlockHeader_test.go index ad8600307a..8bd7349ca3 100644 --- a/data/bookkeeping/lightBlockHeader_test.go +++ b/data/bookkeeping/lightBlockHeader_test.go @@ -20,12 +20,13 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestConvertSha256Header(t *testing.T) { diff --git a/data/committee/credential.go b/data/committee/credential.go index 7d6ce35b1e..6f7e35a8b1 100644 --- a/data/committee/credential.go +++ b/data/committee/credential.go @@ -21,12 +21,13 @@ import ( "fmt" "math/big" + "github.com/algorand/sortition" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" - "github.com/algorand/sortition" ) type ( diff --git a/data/transactions/asset_test.go b/data/transactions/asset_test.go index f12f19a716..33261fa95b 100644 --- a/data/transactions/asset_test.go +++ b/data/transactions/asset_test.go @@ -21,11 +21,12 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestAxferWellFormedErrors(t *testing.T) { diff --git a/data/transactions/heartbeat_test.go b/data/transactions/heartbeat_test.go index 538901dccc..e5b898a865 100644 --- a/data/transactions/heartbeat_test.go +++ b/data/transactions/heartbeat_test.go @@ -20,14 +20,15 @@ import ( "fmt" "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/committee" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestWellFormedHeartbeatErrors(t *testing.T) { diff --git a/data/transactions/json_test.go b/data/transactions/json_test.go index ddaca98413..0ef15db1b4 100644 --- a/data/transactions/json_test.go +++ b/data/transactions/json_test.go @@ -26,12 +26,13 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func decode(t *testing.T, data string, v interface{}) { diff --git a/data/transactions/keyreg_test.go b/data/transactions/keyreg_test.go index 59d5dac223..8684f11a27 100644 --- a/data/transactions/keyreg_test.go +++ b/data/transactions/keyreg_test.go @@ -21,13 +21,14 @@ import ( "fmt" "testing" + "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/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) var generateFlag = flag.Bool("generate", false, "") diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index aba822b138..63638b6e9e 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -34,6 +34,7 @@ import ( "unicode" "github.com/algorand/avm-abi/abi" + "github.com/algorand/go-algorand/data/basics" ) diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index 7aab034ccb..8f9bffd1a1 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -22,11 +22,12 @@ import ( "strings" "testing" + "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/test/partitiontest" - "github.com/stretchr/testify/require" ) // This test ensures a program compiled with by pre-AVM v2 go-algorand diff --git a/data/transactions/logic/crypto.go b/data/transactions/logic/crypto.go index f3e05ee120..38707d4496 100644 --- a/data/transactions/logic/crypto.go +++ b/data/transactions/logic/crypto.go @@ -26,14 +26,15 @@ import ( "hash" "math/big" + bls12_381mimc "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc" + bn254mimc "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" + "golang.org/x/crypto/sha3" + + "github.com/algorand/go-sumhash" + "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" - - bls12_381mimc "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/mimc" - bn254mimc "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc" ) // mimc is implemented for compatibility with zk circuits, diff --git a/data/transactions/logic/debugger_eval_test.go b/data/transactions/logic/debugger_eval_test.go index d2e6bf6efb..208e686165 100644 --- a/data/transactions/logic/debugger_eval_test.go +++ b/data/transactions/logic/debugger_eval_test.go @@ -21,11 +21,12 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" . "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) const debuggerTestProgramApprove string = `intcblock 0 1 1 1 1 5 100 diff --git a/data/transactions/logic/debugger_test.go b/data/transactions/logic/debugger_test.go index fedf24bd9c..ebcb152787 100644 --- a/data/transactions/logic/debugger_test.go +++ b/data/transactions/logic/debugger_test.go @@ -19,8 +19,9 @@ package logic import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestLineToPC(t *testing.T) { diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index a1e3512fef..955345211d 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -20,9 +20,10 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestOpDocs(t *testing.T) { diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index f1a645ffef..9102f8cf6e 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -24,6 +24,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" @@ -31,8 +33,6 @@ import ( "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - - "github.com/stretchr/testify/require" ) func TestInnerTypesV5(t *testing.T) { diff --git a/data/transactions/logic/evalBench_test.go b/data/transactions/logic/evalBench_test.go index 229f1780bc..226379ccb7 100644 --- a/data/transactions/logic/evalBench_test.go +++ b/data/transactions/logic/evalBench_test.go @@ -19,12 +19,13 @@ package logic_test import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) func BenchmarkCheckSignature(b *testing.B) { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 5c81bcaf41..d088b60581 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "pgregory.net/rapid" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/config/bounds" @@ -41,8 +42,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util" - - "pgregory.net/rapid" ) type protoOpt func(*config.ConsensusParams) diff --git a/data/transactions/logic/jsonspec_test.go b/data/transactions/logic/jsonspec_test.go index 3e78b4547c..63f047e168 100644 --- a/data/transactions/logic/jsonspec_test.go +++ b/data/transactions/logic/jsonspec_test.go @@ -22,10 +22,11 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" "golang.org/x/text/encoding/unicode" "golang.org/x/text/encoding/unicode/utf32" + + "github.com/algorand/go-algorand/test/partitiontest" ) // As of go1.10, json implements encoding and decoding of JSON as defined in RFC 7159. https://pkg.go.dev/encoding/json diff --git a/data/transactions/logic/mocktracer/tracer.go b/data/transactions/logic/mocktracer/tracer.go index 050fa7bbe4..19c98cbd64 100644 --- a/data/transactions/logic/mocktracer/tracer.go +++ b/data/transactions/logic/mocktracer/tracer.go @@ -19,14 +19,15 @@ package mocktracer import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "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/ledgercore" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // EventType represents a type of logic.EvalTracer event diff --git a/data/transactions/logic/opcodes_test.go b/data/transactions/logic/opcodes_test.go index bdb8fb4441..43532c3ae5 100644 --- a/data/transactions/logic/opcodes_test.go +++ b/data/transactions/logic/opcodes_test.go @@ -22,8 +22,9 @@ import ( "slices" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestOpSpecs(t *testing.T) { diff --git a/data/transactions/logic/pairing.go b/data/transactions/logic/pairing.go index 25acf7a280..22941bf1ce 100644 --- a/data/transactions/logic/pairing.go +++ b/data/transactions/logic/pairing.go @@ -21,13 +21,12 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark-crypto/ecc/bn254" - bn254fp "github.com/consensys/gnark-crypto/ecc/bn254/fp" - bn254fr "github.com/consensys/gnark-crypto/ecc/bn254/fr" - bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" bls12381fp "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" bls12381fr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/ecc/bn254" + bn254fp "github.com/consensys/gnark-crypto/ecc/bn254/fp" + bn254fr "github.com/consensys/gnark-crypto/ecc/bn254/fr" ) type sError string diff --git a/data/transactions/logic/pairing_test.go b/data/transactions/logic/pairing_test.go index f97e4e57c4..32a4676aba 100644 --- a/data/transactions/logic/pairing_test.go +++ b/data/transactions/logic/pairing_test.go @@ -24,7 +24,6 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" bls12381fp "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" bls12381fr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" @@ -32,6 +31,8 @@ import ( bn254fp "github.com/consensys/gnark-crypto/ecc/bn254/fp" bn254fr "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) const pairingNonsense = ` diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index 11691a0bc5..c93a91d81c 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -22,13 +22,14 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "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/data/txntest" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // TestAppSharing confirms that as of v9, apps can be accessed across groups, diff --git a/data/transactions/logic/sourcemap_test.go b/data/transactions/logic/sourcemap_test.go index 04159bdc90..5f23231792 100644 --- a/data/transactions/logic/sourcemap_test.go +++ b/data/transactions/logic/sourcemap_test.go @@ -19,8 +19,9 @@ package logic import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestGetSourceMap(t *testing.T) { diff --git a/data/transactions/logic/tracer_test.go b/data/transactions/logic/tracer_test.go index 386c9ee3a7..0ea5124fbf 100644 --- a/data/transactions/logic/tracer_test.go +++ b/data/transactions/logic/tracer_test.go @@ -19,11 +19,12 @@ package logic_test import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" . "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) type tracerTestCase struct { diff --git a/data/transactions/payset_test.go b/data/transactions/payset_test.go index 33f99dafbf..11dc6eded2 100644 --- a/data/transactions/payset_test.go +++ b/data/transactions/payset_test.go @@ -19,8 +19,9 @@ package transactions import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func generatePayset(txnCount, acctCount int) Payset { diff --git a/data/transactions/stateproof_test.go b/data/transactions/stateproof_test.go index 6ef95e8d6d..18acf8e56d 100644 --- a/data/transactions/stateproof_test.go +++ b/data/transactions/stateproof_test.go @@ -20,6 +20,8 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/stateproof" @@ -27,7 +29,6 @@ import ( "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) type stateproofTxnTestCase struct { diff --git a/data/transactions/teal_test.go b/data/transactions/teal_test.go index 954fe501ba..e7095188ef 100644 --- a/data/transactions/teal_test.go +++ b/data/transactions/teal_test.go @@ -20,10 +20,11 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestEvalDeltaEqual(t *testing.T) { diff --git a/data/transactions/verify/artifact_test.go b/data/transactions/verify/artifact_test.go index 748d7dccab..e7569f20b6 100644 --- a/data/transactions/verify/artifact_test.go +++ b/data/transactions/verify/artifact_test.go @@ -19,14 +19,14 @@ package verify import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "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/data/txntest" "github.com/algorand/go-algorand/protocol" - - "github.com/stretchr/testify/require" ) // test/benchmark real programs found in the wild (testnet/mainnet). diff --git a/data/txDupCache.go b/data/txDupCache.go index 92b7087721..3f85c910af 100644 --- a/data/txDupCache.go +++ b/data/txDupCache.go @@ -23,11 +23,12 @@ import ( "sync" "time" - "github.com/algorand/go-algorand/config/bounds" - "github.com/algorand/go-algorand/crypto" + "golang.org/x/crypto/blake2b" + "github.com/algorand/go-deadlock" - "golang.org/x/crypto/blake2b" + "github.com/algorand/go-algorand/config/bounds" + "github.com/algorand/go-algorand/crypto" ) // digestCache is a rotating cache of size N accepting crypto.Digest as a key diff --git a/data/txDupCache_test.go b/data/txDupCache_test.go index 79899b313d..ac0f9be0ee 100644 --- a/data/txDupCache_test.go +++ b/data/txDupCache_test.go @@ -25,12 +25,12 @@ import ( "time" "github.com/stretchr/testify/require" + "golang.org/x/crypto/blake2b" - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-deadlock" - "golang.org/x/crypto/blake2b" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/test/partitiontest" ) func TestTxHandlerDigestCache(t *testing.T) { diff --git a/data/txHandler.go b/data/txHandler.go index db3f8e03a5..d42824da2a 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -25,6 +25,8 @@ import ( "sync" "time" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/config/bounds" "github.com/algorand/go-algorand/crypto" @@ -38,7 +40,6 @@ import ( "github.com/algorand/go-algorand/util" "github.com/algorand/go-algorand/util/execpool" "github.com/algorand/go-algorand/util/metrics" - "github.com/algorand/go-deadlock" ) var transactionMessagesHandled = metrics.MakeCounter(metrics.TransactionMessagesHandled) diff --git a/data/txntest/defi.go b/data/txntest/defi.go index 490fc35b86..027badccc3 100644 --- a/data/txntest/defi.go +++ b/data/txntest/defi.go @@ -19,11 +19,12 @@ package txntest import ( "testing" + "github.com/stretchr/testify/require" + "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/data/transactions/logic" - "github.com/stretchr/testify/require" ) // Test/benchmark real programs found in the wild (testnet/mainnet). diff --git a/gen/generate_test.go b/gen/generate_test.go index c02bf5bd63..e8207bb107 100644 --- a/gen/generate_test.go +++ b/gen/generate_test.go @@ -27,14 +27,13 @@ import ( "sync" "testing" - "github.com/algorand/go-algorand/data/basics" - "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/account" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" diff --git a/heartbeat/service_test.go b/heartbeat/service_test.go index 652cfe153d..8fcc9a7056 100644 --- a/heartbeat/service_test.go +++ b/heartbeat/service_test.go @@ -21,6 +21,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" @@ -32,8 +36,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/go-deadlock" - "github.com/stretchr/testify/require" ) type table map[basics.Address]ledgercore.AccountData diff --git a/internal/rapidgen/rapidgenerators.go b/internal/rapidgen/rapidgenerators.go index d8fcf0cace..1f31da168b 100644 --- a/internal/rapidgen/rapidgenerators.go +++ b/internal/rapidgen/rapidgenerators.go @@ -20,8 +20,9 @@ package rapidgen import ( "fmt" - "pgregory.net/rapid" "strings" + + "pgregory.net/rapid" ) // DomainWithPort generates an RFC 1035 compliant domain name with a port. diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 1e685e3ca8..6efec70595 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -36,6 +36,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/avm-abi/apps" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/config/bounds" "github.com/algorand/go-algorand/crypto" @@ -3674,3 +3675,468 @@ func TestOnlineAccountsSuspended(t *testing.T) { require.NoError(t, err) require.Len(t, updated, 0) } + +// TestLookupAssetResourcesWithDeltas verifies that lookupAssetResources properly merges +// in-memory deltas with database results to return current-round data. +// It commits resources to DB, then adds uncommitted delta modifications across two rounds, +// and checks the merged view covers: new creations, holding deletions, holding modifications, +// params-only modifications, params deletions with holding retained, and multi-round +// backwards walking that picks the most recent delta. +func TestLookupAssetResourcesWithDeltas(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + testProtocolVersion := protocol.ConsensusCurrentVersion + protoParams := config.Consensus[testProtocolVersion] + + accts := setupAccts(5) + + var creatorAddr, optinAddr basics.Address + for addr := range accts[0] { + if addr != testSinkAddr && addr != testPoolAddr { + if creatorAddr.IsZero() { + creatorAddr = addr + } else { + optinAddr = addr + break + } + } + } + + ml := makeMockLedgerForTracker(t, true, 1, testProtocolVersion, accts) + defer ml.Close() + + conf := config.GetDefaultLocal() + // Zero lookback so all committed rounds flush immediately, leaving deltas[0] + // as the first uncommitted round. This is critical for testing that the delta + // loop visits index 0. + conf.MaxAcctLookback = 0 + au, _ := newAcctUpdates(t, ml, conf) + + knownCreatables := make(map[basics.CreatableIndex]bool) + + // Round 1: create assets 1000-1005 with params and holdings + // 1000: will have holding modified, then overridden in a second delta round + // 1001: will have holding deleted in delta (params remain since account is creator) + // 1002: will remain unchanged + // 1003: will have params-only modification in delta + // 1004: will have params deleted in delta (holding remains) + // 1005: will have both holding and params deleted in delta + // 1007: will be fully deleted in a committed round; optinAddr opts in with zero balance + { + var updates ledgercore.AccountDeltas + updates.Upsert(creatorAddr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAssetParams: 7, + TotalAssets: 7, + }, + }) + for assetIdx := uint64(1000); assetIdx <= 1005; assetIdx++ { + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(assetIdx), + ledgercore.AssetParamsDelta{ + Params: &basics.AssetParams{ + Total: assetIdx * 1000, + UnitName: fmt.Sprintf("A%d", assetIdx), + AssetName: fmt.Sprintf("Asset%d", assetIdx), + }, + }, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: assetIdx * 100}, + }) + } + + // 1007: creatorAddr is creator; optinAddr opts in with zero balance and asset will be deleted + // in a committed round to test that surviving holdings of deleted assets are returned. + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1007), + ledgercore.AssetParamsDelta{ + Params: &basics.AssetParams{ + Total: 7000, + UnitName: "A1007", + AssetName: "Asset1007", + }, + }, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: 7000}, + }) + updates.Upsert(optinAddr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAssets: 1, + }, + }) + updates.UpsertAssetResource(optinAddr, basics.AssetIndex(1007), + ledgercore.AssetParamsDelta{}, + ledgercore.AssetHoldingDelta{Holding: &basics.AssetHolding{Amount: 0}}) + + base := accts[0] + newAccts := applyPartialDeltas(base, updates) + accts = append(accts, newAccts) + + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, 1, au, base, opts, nil) + auCommitSync(t, 1, au, ml) + + for assetIdx := uint64(1000); assetIdx <= 1005; assetIdx++ { + knownCreatables[basics.CreatableIndex(assetIdx)] = true + } + knownCreatables[basics.CreatableIndex(1007)] = true + } + + // Round 2 destroys asset 1007; optinAddr's zero holding survives in DB. + // Additional empty rounds (if any) ensure earlier data flushes past MaxAcctLookback. + for i := basics.Round(2); i <= basics.Round(conf.MaxAcctLookback+2); i++ { + var updates ledgercore.AccountDeltas + if i == 2 { + updates.Upsert(creatorAddr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAssetParams: 6, + TotalAssets: 6, + }, + }) + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1007), + ledgercore.AssetParamsDelta{Deleted: true}, + ledgercore.AssetHoldingDelta{Deleted: true}) + } + base := accts[i-1] + newAccts := applyPartialDeltas(base, updates) + accts = append(accts, newAccts) + + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, i, au, base, opts, nil) + auCommitSync(t, i, au, ml) + } + + // Delta round 1 (uncommitted) + deltaRound1 := basics.Round(conf.MaxAcctLookback + 3) + { + var updates ledgercore.AccountDeltas + // 1005: delete both holding and params + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1005), + ledgercore.AssetParamsDelta{Deleted: true}, + ledgercore.AssetHoldingDelta{Deleted: true}) + // 1006: new creation (not in DB) + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1006), + ledgercore.AssetParamsDelta{ + Params: &basics.AssetParams{Total: 6000, UnitName: "A1006"}, + }, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: 6000}, + }) + // 1001: delete holding + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1001), + ledgercore.AssetParamsDelta{}, + ledgercore.AssetHoldingDelta{Deleted: true}) + // 1000: modify holding (will be overridden by delta round 2) + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1000), + ledgercore.AssetParamsDelta{}, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: 9999}, + }) + // 1003: modify params only (holding unchanged) + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1003), + ledgercore.AssetParamsDelta{ + Params: &basics.AssetParams{Total: 7777, UnitName: "A1003new"}, + }, + ledgercore.AssetHoldingDelta{}) + // 1004: delete params (holding remains) + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1004), + ledgercore.AssetParamsDelta{Deleted: true}, + ledgercore.AssetHoldingDelta{}) + + base := accts[deltaRound1-1] + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, deltaRound1, au, base, opts, nil) + } + + // Delta round 2 (uncommitted): override 1000's holding from round 1 + deltaRound2 := deltaRound1 + 1 + { + var updates ledgercore.AccountDeltas + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1000), + ledgercore.AssetParamsDelta{}, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: 5555}, + }) + + base := accts[deltaRound1-1] + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, deltaRound2, au, base, opts, nil) + } + + resources, rnd, err := au.LookupAssetResources(creatorAddr, 0, 100) + require.NoError(t, err) + require.Equal(t, deltaRound2, rnd) + + // Expected: 1000, 1001, 1002, 1003, 1004, 1006. + // 1005 fully deleted (both holding and params) — should not appear. + require.Len(t, resources, 6) + + assetMap := make(map[basics.AssetIndex]ledgercore.AssetResourceWithIDs) + for _, res := range resources { + assetMap[res.AssetID] = res + } + + // 1000: holding from delta round 2 (most recent), params preserved from DB + require.Equal(t, uint64(5555), assetMap[basics.AssetIndex(1000)].AssetHolding.Amount) + require.NotNil(t, assetMap[basics.AssetIndex(1000)].AssetParams) + require.Equal(t, uint64(1_000_000), assetMap[basics.AssetIndex(1000)].AssetParams.Total) + + // 1001: holding deleted but params remain (account is still creator) + require.Contains(t, assetMap, basics.AssetIndex(1001)) + require.Nil(t, assetMap[basics.AssetIndex(1001)].AssetHolding) + require.NotNil(t, assetMap[basics.AssetIndex(1001)].AssetParams) + require.Equal(t, uint64(1_001_000), assetMap[basics.AssetIndex(1001)].AssetParams.Total) + + // 1002: unchanged from DB + require.Equal(t, uint64(1002*100), assetMap[basics.AssetIndex(1002)].AssetHolding.Amount) + require.NotNil(t, assetMap[basics.AssetIndex(1002)].AssetParams) + require.Equal(t, uint64(1_002_000), assetMap[basics.AssetIndex(1002)].AssetParams.Total) + + // 1003: params updated in delta, holding preserved from DB + require.Equal(t, uint64(1003*100), assetMap[basics.AssetIndex(1003)].AssetHolding.Amount) + require.NotNil(t, assetMap[basics.AssetIndex(1003)].AssetParams) + require.Equal(t, uint64(7777), assetMap[basics.AssetIndex(1003)].AssetParams.Total) + require.Equal(t, "A1003new", assetMap[basics.AssetIndex(1003)].AssetParams.UnitName) + + // 1004: params deleted in delta, holding preserved from DB, no creator + require.Equal(t, uint64(1004*100), assetMap[basics.AssetIndex(1004)].AssetHolding.Amount) + require.Nil(t, assetMap[basics.AssetIndex(1004)].AssetParams) + require.True(t, assetMap[basics.AssetIndex(1004)].Creator.IsZero()) + + // 1005: both holding and params deleted — should not appear + require.NotContains(t, assetMap, basics.AssetIndex(1005)) + + // 1006: new creation from delta + require.Equal(t, uint64(6000), assetMap[basics.AssetIndex(1006)].AssetHolding.Amount) + require.NotNil(t, assetMap[basics.AssetIndex(1006)].AssetParams) + require.Equal(t, uint64(6000), assetMap[basics.AssetIndex(1006)].AssetParams.Total) + + // optinAddr opted in to asset 1007 with a zero balance before it was destroyed. The protocol + // does not track all opt-outs when an asset is deleted (only the creator's holding is + // enforced), so optinAddr's holding persists in the DB even after the asset params are gone. + // The lookup must return a non-nil holding with zero amount and no params or creator. + optinAddrResources, optinAddrRnd, err := au.LookupAssetResources(optinAddr, 0, 100) + require.NoError(t, err) + require.Equal(t, deltaRound2, optinAddrRnd) + require.Len(t, optinAddrResources, 1) + require.Equal(t, basics.AssetIndex(1007), optinAddrResources[0].AssetID) + require.NotNil(t, optinAddrResources[0].AssetHolding) + require.Equal(t, basics.AssetHolding{}, *optinAddrResources[0].AssetHolding) + require.Nil(t, optinAddrResources[0].AssetParams) + require.True(t, optinAddrResources[0].Creator.IsZero()) +} + +// TestLookupApplicationResourcesWithDeltas verifies that lookupApplicationResources properly +// merges in-memory deltas with database results to return current-round data. +// It covers: new creation, local state deletion, local state modification, params-only +// modification, params deletion with local state retained, multi-round backwards walking, +// and the includeParams flag. +func TestLookupApplicationResourcesWithDeltas(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + testProtocolVersion := protocol.ConsensusCurrentVersion + protoParams := config.Consensus[testProtocolVersion] + + accts := setupAccts(5) + + var testAddr basics.Address + for addr := range accts[0] { + if addr != testSinkAddr && addr != testPoolAddr { + testAddr = addr + break + } + } + + ml := makeMockLedgerForTracker(t, true, 1, testProtocolVersion, accts) + defer ml.Close() + + conf := config.GetDefaultLocal() + // Zero lookback so all committed rounds flush immediately, leaving deltas[0] + // as the first uncommitted round. This is critical for testing that the delta + // loop visits index 0. + conf.MaxAcctLookback = 0 + au, _ := newAcctUpdates(t, ml, conf) + + knownCreatables := make(map[basics.CreatableIndex]bool) + + // Round 1: create apps 2000-2005 with params and local state + // 2000: will have local state modified, then overridden in a second delta round + // 2001: will have local state deleted in delta (params remain since account is creator) + // 2002: will remain unchanged + // 2003: will have params-only modification in delta + // 2004: will have params deleted in delta (local state remains) + // 2005: will have both local state and params deleted in delta + { + var updates ledgercore.AccountDeltas + updates.Upsert(testAddr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAppParams: 6, + TotalAppLocalStates: 6, + }, + }) + for appIdx := uint64(2000); appIdx <= 2005; appIdx++ { + updates.UpsertAppResource(testAddr, basics.AppIndex(appIdx), + ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: []byte{0x06, 0x81, 0x01}, + GlobalState: basics.TealKeyValue{}, + }, + }, + ledgercore.AppLocalStateDelta{ + LocalState: &basics.AppLocalState{ + Schema: basics.StateSchema{NumUint: appIdx - 2000}, + }, + }) + } + + base := accts[0] + newAccts := applyPartialDeltas(base, updates) + accts = append(accts, newAccts) + + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, 1, au, base, opts, nil) + auCommitSync(t, 1, au, ml) + + for appIdx := uint64(2000); appIdx <= 2005; appIdx++ { + knownCreatables[basics.CreatableIndex(appIdx)] = true + } + } + + // Additional empty rounds (if any) ensure earlier data flushes past MaxAcctLookback into DB. + for i := basics.Round(2); i <= basics.Round(conf.MaxAcctLookback+2); i++ { + var updates ledgercore.AccountDeltas + base := accts[i-1] + newAccts := applyPartialDeltas(base, updates) + accts = append(accts, newAccts) + + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, i, au, base, opts, nil) + auCommitSync(t, i, au, ml) + } + + // Delta round 1 (uncommitted) + deltaRound1 := basics.Round(conf.MaxAcctLookback + 3) + { + var updates ledgercore.AccountDeltas + // 2005: delete both local state and params + updates.UpsertAppResource(testAddr, basics.AppIndex(2005), + ledgercore.AppParamsDelta{Deleted: true}, + ledgercore.AppLocalStateDelta{Deleted: true}) + // 2006: new creation (not in DB) + updates.UpsertAppResource(testAddr, basics.AppIndex(2006), + ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte{0x06, 0x81, 0x01}}, + }, + ledgercore.AppLocalStateDelta{ + LocalState: &basics.AppLocalState{ + Schema: basics.StateSchema{NumUint: 60}, + }, + }) + // 2001: delete local state + updates.UpsertAppResource(testAddr, basics.AppIndex(2001), + ledgercore.AppParamsDelta{}, + ledgercore.AppLocalStateDelta{Deleted: true}) + // 2000: modify local state (will be overridden by delta round 2) + updates.UpsertAppResource(testAddr, basics.AppIndex(2000), + ledgercore.AppParamsDelta{}, + ledgercore.AppLocalStateDelta{ + LocalState: &basics.AppLocalState{ + Schema: basics.StateSchema{NumUint: 99}, + }, + }) + // 2003: modify params only (local state unchanged) + updates.UpsertAppResource(testAddr, basics.AppIndex(2003), + ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ + ApprovalProgram: []byte{0x06, 0x81, 0x02}, + ClearStateProgram: []byte{0x06, 0x81, 0x01}, + }, + }, + ledgercore.AppLocalStateDelta{}) + // 2004: delete params (local state remains) + updates.UpsertAppResource(testAddr, basics.AppIndex(2004), + ledgercore.AppParamsDelta{Deleted: true}, + ledgercore.AppLocalStateDelta{}) + + base := accts[deltaRound1-1] + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, deltaRound1, au, base, opts, nil) + } + + // Delta round 2 (uncommitted): override 2000's local state from round 1 + deltaRound2 := deltaRound1 + 1 + { + var updates ledgercore.AccountDeltas + updates.UpsertAppResource(testAddr, basics.AppIndex(2000), + ledgercore.AppParamsDelta{}, + ledgercore.AppLocalStateDelta{ + LocalState: &basics.AppLocalState{ + Schema: basics.StateSchema{NumUint: 42}, + }, + }) + + base := accts[deltaRound1-1] + opts := auNewBlockOpts{updates, testProtocolVersion, protoParams, knownCreatables} + auNewBlock(t, deltaRound2, au, base, opts, nil) + } + + // includeParams=true + resources, rnd, err := au.LookupApplicationResources(testAddr, 0, 100, true) + require.NoError(t, err) + require.Equal(t, deltaRound2, rnd) + + // Expected: 2000, 2001, 2002, 2003, 2004, 2006. + // 2005 fully deleted (both local state and params) — should not appear. + require.Len(t, resources, 6) + + appMap := make(map[basics.AppIndex]ledgercore.AppResourceWithIDs) + for _, res := range resources { + appMap[res.AppID] = res + } + + // 2000: local state from delta round 2 (most recent), params preserved from DB + require.Equal(t, uint64(42), appMap[basics.AppIndex(2000)].AppLocalState.Schema.NumUint) + require.NotNil(t, appMap[basics.AppIndex(2000)].AppParams) + require.Equal(t, []byte{0x06, 0x81, 0x01}, appMap[basics.AppIndex(2000)].AppParams.ApprovalProgram) + + // 2001: local state deleted but params remain (account is still creator) + require.Contains(t, appMap, basics.AppIndex(2001)) + require.Nil(t, appMap[basics.AppIndex(2001)].AppLocalState) + require.NotNil(t, appMap[basics.AppIndex(2001)].AppParams) + require.Equal(t, []byte{0x06, 0x81, 0x01}, appMap[basics.AppIndex(2001)].AppParams.ApprovalProgram) + + // 2002: unchanged from DB + require.Equal(t, uint64(2), appMap[basics.AppIndex(2002)].AppLocalState.Schema.NumUint) + require.NotNil(t, appMap[basics.AppIndex(2002)].AppParams) + + // 2003: params updated in delta, local state preserved from DB + require.Equal(t, uint64(3), appMap[basics.AppIndex(2003)].AppLocalState.Schema.NumUint) + require.NotNil(t, appMap[basics.AppIndex(2003)].AppParams) + require.Equal(t, []byte{0x06, 0x81, 0x02}, appMap[basics.AppIndex(2003)].AppParams.ApprovalProgram) + require.Equal(t, []byte{0x06, 0x81, 0x01}, appMap[basics.AppIndex(2003)].AppParams.ClearStateProgram) + + // 2004: params deleted in delta, local state preserved from DB, no creator + require.Equal(t, uint64(4), appMap[basics.AppIndex(2004)].AppLocalState.Schema.NumUint) + require.Nil(t, appMap[basics.AppIndex(2004)].AppParams) + require.True(t, appMap[basics.AppIndex(2004)].Creator.IsZero()) + + // 2005: both local state and params deleted — should not appear + require.NotContains(t, appMap, basics.AppIndex(2005)) + + // 2006: new creation from delta + require.Equal(t, uint64(60), appMap[basics.AppIndex(2006)].AppLocalState.Schema.NumUint) + require.NotNil(t, appMap[basics.AppIndex(2006)].AppParams) + + // includeParams=false should omit AppParams from all results + resourcesNoParams, _, err := au.LookupApplicationResources(testAddr, 0, 100, false) + require.NoError(t, err) + require.Len(t, resourcesNoParams, 5) + + for _, res := range resourcesNoParams { + require.Nil(t, res.AppParams, "AppParams should be nil when includeParams=false") + } +} diff --git a/ledger/acctonline_expired_test.go b/ledger/acctonline_expired_test.go index c35667316a..c748fa0e69 100644 --- a/ledger/acctonline_expired_test.go +++ b/ledger/acctonline_expired_test.go @@ -23,6 +23,8 @@ import ( "strconv" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -32,7 +34,6 @@ import ( ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // onlineAcctModel provides a simple interface for tracking accounts diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index 4327f2ae24..4d4008668e 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -25,6 +25,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -35,7 +37,6 @@ import ( ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func commitSync(t *testing.T, oa *onlineAccounts, ml *mockLedgerForTracker, rnd basics.Round) { diff --git a/ledger/acctonlineexp.go b/ledger/acctonlineexp.go index 0a772e6b9b..c7f62dcf9e 100644 --- a/ledger/acctonlineexp.go +++ b/ledger/acctonlineexp.go @@ -17,8 +17,9 @@ package ledger import ( - "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/data/basics" ) type expiredCirculationCache struct { diff --git a/ledger/acctonlineexp_test.go b/ledger/acctonlineexp_test.go index 3f5b943b4e..e124cd2bf4 100644 --- a/ledger/acctonlineexp_test.go +++ b/ledger/acctonlineexp_test.go @@ -19,9 +19,10 @@ package ledger import ( "testing" + "github.com/stretchr/testify/require" + "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) { diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 64257813b9..aedf786e15 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -17,10 +17,12 @@ package ledger import ( + "cmp" "context" "errors" "fmt" "io" + "slices" "sort" "strings" "sync" @@ -345,6 +347,10 @@ func (au *accountUpdates) LookupAssetResources(addr basics.Address, assetIDGT ba return au.lookupAssetResources(addr, assetIDGT, limit) } +func (au *accountUpdates) LookupApplicationResources(addr basics.Address, appIDGT basics.AppIndex, limit uint64, includeParams bool) ([]ledgercore.AppResourceWithIDs, basics.Round, error) { + return au.lookupApplicationResources(addr, appIDGT, limit, includeParams) +} + func (au *accountUpdates) LookupKv(rnd basics.Round, key string) ([]byte, error) { return au.lookupKv(rnd, key, true /* take lock */) } @@ -1212,53 +1218,301 @@ func (au *accountUpdates) lookupResource(rnd basics.Round, addr basics.Address, } } -// lookupAssetResources 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 +// lookupAssetResources returns all the asset resources for a given address. +// It merges in-memory deltas with persisted data to provide current-round information. +func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT basics.AssetIndex, limit uint64) ([]ledgercore.AssetResourceWithIDs, basics.Round, error) { + needUnlock := true + au.accountsMu.RLock() + defer func() { + if needUnlock { + au.accountsMu.RUnlock() + } + }() + + if limit == 0 { + return nil, au.cachedDBRound + basics.Round(len(au.deltas)), nil } - data = make([]ledgercore.AssetResourceWithIDs, 0, len(persistedResources)) - for _, pd := range persistedResources { - ah := pd.Data.GetAssetHolding() + for { + currentDBRound := au.cachedDBRound + currentDeltaLen := len(au.deltas) + + // Walk deltas backwards; the first entry found for a given asset is the most recent. + deltaResults := make(map[basics.AssetIndex]ledgercore.AssetResourceRecord) + numDeltaDeleted := 0 + + for i := currentDeltaLen; i > 0; { + i-- + for _, rec := range au.deltas[i].Accts.AssetResources { + if rec.Addr != addr || rec.Aidx <= assetIDGT { + continue + } + if _, ok := deltaResults[rec.Aidx]; ok { + continue + } + deltaResults[rec.Aidx] = rec + if rec.Holding.Deleted { + numDeltaDeleted++ + } + } + } + + retRound := currentDBRound + basics.Round(currentDeltaLen) + + au.accountsMu.RUnlock() + needUnlock = false - var arwi ledgercore.AssetResourceWithIDs - if !pd.Creator.IsZero() { - ap := pd.Data.GetAssetParams() + // Over-request from DB to compensate for delta deletions that remove DB rows + // from the result set. Deletions are the only delta entries that shrink the + // page — modifications and new creations cannot reduce the DB contribution. + dbLimit := limit + uint64(numDeltaDeleted) + + persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(assetIDGT), dbLimit, basics.AssetCreatable) + if err != nil { + return nil, 0, err + } - arwi = ledgercore.AssetResourceWithIDs{ - AssetID: basics.AssetIndex(pd.Aidx), - Creator: pd.Creator, + if resourceDbRound == currentDBRound { + seenInDB := make(map[basics.AssetIndex]bool, len(persistedResources)) + result := make([]ledgercore.AssetResourceWithIDs, 0, limit) - AssetResource: ledgercore.AssetResource{ - AssetHolding: &ah, - AssetParams: &ap, - }, + // Determine the upper bound of the DB page so we only add delta entries + // within range and don't accidentally set a next-token that skips items. + var dbHasMore bool + var dbMaxID basics.AssetIndex + if len(persistedResources) > 0 { + dbMaxID = basics.AssetIndex(persistedResources[len(persistedResources)-1].Aidx) + dbHasMore = uint64(len(persistedResources)) == dbLimit } - } else { - arwi = ledgercore.AssetResourceWithIDs{ - AssetID: basics.AssetIndex(pd.Aidx), - AssetResource: ledgercore.AssetResource{ - AssetHolding: &ah, - }, + for _, pd := range persistedResources { + assetID := basics.AssetIndex(pd.Aidx) + seenInDB[assetID] = true + + d, inDelta := deltaResults[assetID] + + arwi := ledgercore.AssetResourceWithIDs{AssetID: assetID} + + if inDelta && d.Holding.Deleted { + // Holding removed by delta — leave AssetHolding nil. + } else if inDelta && d.Holding.Holding != nil { + arwi.AssetHolding = d.Holding.Holding + } else { + ah := pd.Data.GetAssetHolding() + arwi.AssetHolding = &ah + } + + if inDelta && d.Params.Deleted { + // Delta deleted params — omit creator and params. + } else if inDelta && d.Params.Params != nil { + arwi.Creator = pd.Creator + arwi.AssetParams = d.Params.Params + } else if !pd.Creator.IsZero() { + arwi.Creator = pd.Creator + ap := pd.Data.GetAssetParams() + arwi.AssetParams = &ap + } + + if arwi.AssetHolding != nil || arwi.AssetParams != nil { + result = append(result, arwi) + } } + + // Add assets that exist only in deltas (new creations not yet in DB). + // Only include delta entries within the DB page range to avoid setting + // a next-token that would skip items still in the database. + for assetID, d := range deltaResults { + if seenInDB[assetID] { + continue + } + if dbHasMore && assetID > dbMaxID { + continue + } + arwi := ledgercore.AssetResourceWithIDs{AssetID: assetID} + if !d.Holding.Deleted && d.Holding.Holding != nil { + arwi.AssetHolding = d.Holding.Holding + } + if !d.Params.Deleted && d.Params.Params != nil { + arwi.Creator = addr + arwi.AssetParams = d.Params.Params + } + if arwi.AssetHolding != nil || arwi.AssetParams != nil { + result = append(result, arwi) + } + } + + slices.SortFunc(result, func(a, b ledgercore.AssetResourceWithIDs) int { + return cmp.Compare(a.AssetID, b.AssetID) + }) + if uint64(len(result)) > limit { + result = result[:limit] + } + + return result, retRound, nil } - 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 { + if resourceDbRound < currentDBRound { + au.log.Errorf("accountUpdates.lookupAssetResources: database round %d is behind in-memory round %d", resourceDbRound, currentDBRound) + return nil, 0, &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} + } au.accountsMu.RLock() - currentDbRound = au.cachedDBRound + needUnlock = true + for currentDBRound >= au.cachedDBRound && currentDeltaLen == len(au.deltas) { + au.accountsReadCond.Wait() + } + } +} + +// lookupApplicationResources returns all the application resources for a given address. +// It merges in-memory deltas with persisted data to provide current-round information. +// If includeParams is false, AppParams will not be populated to save memory allocations (app params can be ~50KB each). +func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDGT basics.AppIndex, limit uint64, includeParams bool) ([]ledgercore.AppResourceWithIDs, basics.Round, error) { + needUnlock := true + au.accountsMu.RLock() + defer func() { + if needUnlock { + au.accountsMu.RUnlock() + } + }() + + if limit == 0 { + return nil, au.cachedDBRound + basics.Round(len(au.deltas)), nil + } + + for { + currentDBRound := au.cachedDBRound + currentDeltaLen := len(au.deltas) + + // Walk deltas backwards; the first entry found for a given app is the most recent. + deltaResults := make(map[basics.AppIndex]ledgercore.AppResourceRecord) + numDeltaDeleted := 0 + + for i := currentDeltaLen; i > 0; { + i-- + for _, rec := range au.deltas[i].Accts.AppResources { + if rec.Addr != addr || rec.Aidx <= appIDGT { + continue + } + if _, ok := deltaResults[rec.Aidx]; ok { + continue + } + deltaResults[rec.Aidx] = rec + if rec.State.Deleted { + numDeltaDeleted++ + } + } + } + + retRound := currentDBRound + basics.Round(currentDeltaLen) + au.accountsMu.RUnlock() + needUnlock = false + + // Over-request from DB to compensate for delta deletions that remove DB rows + // from the result set. Deletions are the only delta entries that shrink the + // page — modifications and new creations cannot reduce the DB contribution. + dbLimit := limit + uint64(numDeltaDeleted) + + persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(appIDGT), dbLimit, basics.AppCreatable) + if err != nil { + return nil, 0, err + } + + if resourceDbRound == currentDBRound { + seenInDB := make(map[basics.AppIndex]bool, len(persistedResources)) + result := make([]ledgercore.AppResourceWithIDs, 0, limit) + + // Determine the upper bound of the DB page so we only add delta entries + // within range and don't accidentally set a next-token that skips items. + var dbHasMore bool + var dbMaxID basics.AppIndex + if len(persistedResources) > 0 { + dbMaxID = basics.AppIndex(persistedResources[len(persistedResources)-1].Aidx) + dbHasMore = uint64(len(persistedResources)) == dbLimit + } + + for _, pd := range persistedResources { + appID := basics.AppIndex(pd.Aidx) + seenInDB[appID] = true + + d, inDelta := deltaResults[appID] + + arwi := ledgercore.AppResourceWithIDs{AppID: appID} + + if inDelta && d.State.Deleted { + // Local state removed by delta — leave AppLocalState nil. + } else if inDelta && d.State.LocalState != nil { + arwi.AppLocalState = d.State.LocalState + } else { + als := pd.Data.GetAppLocalState() + arwi.AppLocalState = &als + } + + if inDelta && d.Params.Deleted { + // Delta deleted params — omit creator and params. + } else if inDelta && d.Params.Params != nil { + arwi.Creator = pd.Creator + if includeParams { + arwi.AppResource.AppParams = d.Params.Params + } + } else if !pd.Creator.IsZero() { + arwi.Creator = pd.Creator + if includeParams { + ap := pd.Data.GetAppParams() + arwi.AppResource.AppParams = &ap + } + } + + if arwi.AppLocalState != nil || arwi.AppParams != nil { + result = append(result, arwi) + } + } + + // Add apps that exist only in deltas (new opt-ins not yet in DB). + // Only include delta entries within the DB page range to avoid setting + // a next-token that would skip items still in the database. + for appID, d := range deltaResults { + if seenInDB[appID] { + continue + } + if dbHasMore && appID > dbMaxID { + continue + } + arwi := ledgercore.AppResourceWithIDs{AppID: appID} + if !d.State.Deleted && d.State.LocalState != nil { + arwi.AppLocalState = d.State.LocalState + } + if !d.Params.Deleted && d.Params.Params != nil { + arwi.Creator = addr + if includeParams { + arwi.AppResource.AppParams = d.Params.Params + } + } + if arwi.AppLocalState != nil || arwi.AppParams != nil { + result = append(result, arwi) + } + } + + slices.SortFunc(result, func(a, b ledgercore.AppResourceWithIDs) int { + return cmp.Compare(a.AppID, b.AppID) + }) + if uint64(len(result)) > limit { + result = result[:limit] + } + + return result, retRound, nil + } + + if resourceDbRound < currentDBRound { + au.log.Errorf("accountUpdates.lookupApplicationResources: database round %d is behind in-memory round %d", resourceDbRound, currentDBRound) + return nil, 0, &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} + } + au.accountsMu.RLock() + needUnlock = true + for currentDBRound >= au.cachedDBRound && currentDeltaLen == len(au.deltas) { + au.accountsReadCond.Wait() + } } - return data, currentDbRound, nil } func (au *accountUpdates) lookupStateDelta(rnd basics.Round) (ledgercore.StateDelta, error) { diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 4017a26835..4666bb1cb2 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -32,6 +32,8 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/avm-abi/apps" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -45,7 +47,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" - "github.com/algorand/go-deadlock" ) var testPoolAddr = 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} diff --git a/ledger/apply/challenge_test.go b/ledger/apply/challenge_test.go index b0362a846b..15e6e55343 100644 --- a/ledger/apply/challenge_test.go +++ b/ledger/apply/challenge_test.go @@ -19,13 +19,14 @@ package apply import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestBitsMatch(t *testing.T) { diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index e1af25a8d9..9e79f66bcf 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -24,6 +24,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" @@ -33,7 +35,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) var boxAppSource = main(` diff --git a/ledger/bulletin_test.go b/ledger/bulletin_test.go index d5ecaa8558..402b959073 100644 --- a/ledger/bulletin_test.go +++ b/ledger/bulletin_test.go @@ -20,9 +20,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) const epsilon = 5 * time.Millisecond diff --git a/ledger/catchpointfilewriter_test.go b/ledger/catchpointfilewriter_test.go index b534995eb7..05874a53aa 100644 --- a/ledger/catchpointfilewriter_test.go +++ b/ledger/catchpointfilewriter_test.go @@ -35,6 +35,8 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/avm-abi/apps" + "github.com/algorand/msgp/msgp" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merkletrie" @@ -48,7 +50,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/msgp/msgp" ) type decodedCatchpointChunkData struct { @@ -848,6 +849,11 @@ func TestFullCatchpointWriter(t *testing.T) { // another approach is to modify the test and craft round numbers, // and make the ledger to generate catchpoint itself when it is time func testCatchpointFlushRound(l *Ledger) (basics.Round, basics.Round) { + // Ensure all blocks are persisted to the block DB before flushing trackers. + // Without this, LatestCommitted() may return a stale round if the blockQueue + // syncer hasn't finished yet. + l.WaitForCommit(l.Latest()) + // Clear the timer to ensure a flush l.trackers.mu.Lock() l.trackers.lastFlushTime = time.Time{} @@ -1035,6 +1041,7 @@ func TestCatchpointAfterTxns(t *testing.T) { catchpointDataFilePath := filepath.Join(tempDir, t.Name()+".data") catchpointFilePath := filepath.Join(tempDir, t.Name()+".catchpoint.tar.gz") + testCatchpointFlushRound(dl.validator) cph := testWriteCatchpoint(t, config.Consensus[proto], dl.validator.trackerDB(), catchpointDataFilePath, catchpointFilePath, 0, 0) require.EqualValues(t, 3, cph.TotalChunks) @@ -1051,6 +1058,7 @@ func TestCatchpointAfterTxns(t *testing.T) { dl.fullBlock(&newacctpay) // Write and read back in, and ensure even the last effect exists. + testCatchpointFlushRound(dl.validator) cph = testWriteCatchpoint(t, config.Consensus[proto], dl.validator.trackerDB(), catchpointDataFilePath, catchpointFilePath, 0, 0) require.EqualValues(t, cph.TotalChunks, 3) // Still only 3 chunks, as last was in a recent block @@ -1067,6 +1075,7 @@ func TestCatchpointAfterTxns(t *testing.T) { dl.fullBlock(pay.Noted(strconv.Itoa(i))) } + testCatchpointFlushRound(dl.validator) cph = testWriteCatchpoint(t, config.Consensus[proto], dl.validator.trackerDB(), catchpointDataFilePath, catchpointFilePath, 0, 0) require.EqualValues(t, cph.TotalChunks, 4) diff --git a/ledger/catchpointtracker.go b/ledger/catchpointtracker.go index d95d299ea6..9cb214efc0 100644 --- a/ledger/catchpointtracker.go +++ b/ledger/catchpointtracker.go @@ -33,9 +33,10 @@ import ( "sync/atomic" "time" - "github.com/algorand/go-deadlock" "github.com/golang/snappy" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merkletrie" diff --git a/ledger/catchupaccessor_test.go b/ledger/catchupaccessor_test.go index 87cb32aed8..77ef32f488 100644 --- a/ledger/catchupaccessor_test.go +++ b/ledger/catchupaccessor_test.go @@ -28,6 +28,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/msgp/msgp" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -41,7 +43,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" - "github.com/algorand/msgp/msgp" ) func createTestingEncodedChunks(accountsCount uint64) (encodedAccountChunks [][]byte, last64KIndex int) { diff --git a/ledger/double_test.go b/ledger/double_test.go index 728d4e65c6..73e31fd932 100644 --- a/ledger/double_test.go +++ b/ledger/double_test.go @@ -19,6 +19,8 @@ package ledger import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" @@ -28,7 +30,6 @@ import ( "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) // DoubleLedger allows for easy "Double Entry bookkeeping" as a way to write diff --git a/ledger/encoded/recordsV5.go b/ledger/encoded/recordsV5.go index 12c133c251..91506f5319 100644 --- a/ledger/encoded/recordsV5.go +++ b/ledger/encoded/recordsV5.go @@ -17,8 +17,9 @@ package encoded import ( - "github.com/algorand/go-algorand/data/basics" "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/data/basics" ) // BalanceRecordV5 is the encoded account balance record. diff --git a/ledger/encoded/recordsV6.go b/ledger/encoded/recordsV6.go index 12df070e64..232ee6d019 100644 --- a/ledger/encoded/recordsV6.go +++ b/ledger/encoded/recordsV6.go @@ -17,9 +17,10 @@ package encoded import ( + "github.com/algorand/msgp/msgp" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" - "github.com/algorand/msgp/msgp" ) // Adjust these to be big enough for boxes, but not directly tied to box values. diff --git a/ledger/encoded/recordsV6_test.go b/ledger/encoded/recordsV6_test.go index 42fb6c2e55..9bc973c6f5 100644 --- a/ledger/encoded/recordsV6_test.go +++ b/ledger/encoded/recordsV6_test.go @@ -20,12 +20,14 @@ import ( "math" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/avm-abi/apps" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestEncodedKVRecordV6Allocbounds(t *testing.T) { diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index 08dd68e587..b0ff12fa31 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/algorand/avm-abi/apps" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/apply" diff --git a/ledger/eval/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go index 433f518172..7cb43ebb49 100644 --- a/ledger/eval/prefetcher/prefetcher_alignment_test.go +++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go @@ -24,6 +24,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -38,7 +40,6 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/go-deadlock" ) type creatable struct { diff --git a/ledger/evalbench_test.go b/ledger/evalbench_test.go index acd855d6b4..4a1062c5e0 100644 --- a/ledger/evalbench_test.go +++ b/ledger/evalbench_test.go @@ -27,9 +27,10 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" diff --git a/ledger/ledger.go b/ledger/ledger.go index e1c962129f..9cf818c7e6 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -597,10 +597,22 @@ func (l *Ledger) LookupAsset(rnd basics.Round, addr basics.Address, aidx basics. // 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) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + resources, lookupRound, err := l.accts.LookupAssetResources(addr, assetIDGT, limit) return resources, lookupRound, err } +// LookupApplications returns the application resources (local state and params) for a given address, with pagination support. +// If includeParams is false, AppParams will not be populated to save memory allocations. +func (l *Ledger) LookupApplications(addr basics.Address, appIDGT basics.AppIndex, limit uint64, includeParams bool) ([]ledgercore.AppResourceWithIDs, basics.Round, error) { + l.trackerMu.RLock() + defer l.trackerMu.RUnlock() + + return l.accts.LookupApplicationResources(addr, appIDGT, limit, includeParams) +} + // 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_perf_test.go b/ledger/ledger_perf_test.go index 754b8319da..f77145ab7d 100644 --- a/ledger/ledger_perf_test.go +++ b/ledger/ledger_perf_test.go @@ -24,9 +24,10 @@ import ( "strings" "testing" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 55b28c4e3c..97dd9aaec2 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -31,6 +31,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -51,7 +53,6 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" "github.com/algorand/go-algorand/util/execpool" - "github.com/algorand/go-deadlock" ) const preReleaseDBVersion = 6 diff --git a/ledger/ledgercore/accountdata_test.go b/ledger/ledgercore/accountdata_test.go index caa1a672aa..a6dd01e298 100644 --- a/ledger/ledgercore/accountdata_test.go +++ b/ledger/ledgercore/accountdata_test.go @@ -19,10 +19,11 @@ package ledgercore import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // TestBasicsAccountDataRoundtripConversion ensures that basics.AccountData can be converted to diff --git a/ledger/ledgercore/accountresource.go b/ledger/ledgercore/accountresource.go index 8a08c7b935..0cf206ffb3 100644 --- a/ledger/ledgercore/accountresource.go +++ b/ledger/ledgercore/accountresource.go @@ -48,6 +48,14 @@ type AppResource struct { AppParams *basics.AppParams } +// AppResourceWithIDs is used to retrieve an app resource information from the data tier, +// inclusive of the app ID and creator address +type AppResourceWithIDs struct { + AppResource + AppID basics.AppIndex + Creator basics.Address +} + // AssignAccountResourceToAccountData assigns the Asset/App params/holdings contained // in the AccountResource to the given basics.AccountData, creating maps if necessary. // Returns true if the AccountResource contained a new or updated resource, diff --git a/ledger/perf_test.go b/ledger/perf_test.go index 7b76483d33..d8cf5f6497 100644 --- a/ledger/perf_test.go +++ b/ledger/perf_test.go @@ -21,9 +21,10 @@ import ( "fmt" "testing" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" diff --git a/ledger/simple_test.go b/ledger/simple_test.go index 638f8055b7..250067b3e6 100644 --- a/ledger/simple_test.go +++ b/ledger/simple_test.go @@ -23,6 +23,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -37,7 +39,6 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) type simpleLedgerCfg struct { diff --git a/ledger/simulation/resources_test.go b/ledger/simulation/resources_test.go index c7f20c0b6a..c060863158 100644 --- a/ledger/simulation/resources_test.go +++ b/ledger/simulation/resources_test.go @@ -19,13 +19,14 @@ package simulation import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "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/partitiontest" - "github.com/stretchr/testify/require" ) func TestAppAccounts(t *testing.T) { diff --git a/ledger/simulation/simulation_eval_test.go b/ledger/simulation/simulation_eval_test.go index 7e60d829e0..4cb4ae5f44 100644 --- a/ledger/simulation/simulation_eval_test.go +++ b/ledger/simulation/simulation_eval_test.go @@ -25,6 +25,9 @@ import ( "strings" "testing" + "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/data/transactions" @@ -38,9 +41,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func uint64ToBytes(num uint64) []byte { diff --git a/ledger/simulation/simulator_test.go b/ledger/simulation/simulator_test.go index 7f416021a6..62baa5bf4a 100644 --- a/ledger/simulation/simulator_test.go +++ b/ledger/simulation/simulator_test.go @@ -21,6 +21,8 @@ import ( "slices" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data" "github.com/algorand/go-algorand/data/basics" @@ -34,7 +36,6 @@ import ( simulationtesting "github.com/algorand/go-algorand/ledger/simulation/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // We want to be careful that the Algod ledger does not move on to another round diff --git a/ledger/simulation/testing/utils.go b/ledger/simulation/testing/utils.go index 0910e0061f..099d0810e1 100644 --- a/ledger/simulation/testing/utils.go +++ b/ledger/simulation/testing/utils.go @@ -21,6 +21,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -35,7 +37,6 @@ import ( ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) // Account contains public and private keys, as well as the state of an account diff --git a/ledger/simulation/tracer_test.go b/ledger/simulation/tracer_test.go index 8335d5ac43..6d07c9d794 100644 --- a/ledger/simulation/tracer_test.go +++ b/ledger/simulation/tracer_test.go @@ -19,11 +19,12 @@ package simulation import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/logic/mocktracer" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestCursorEvalTracer(t *testing.T) { diff --git a/ledger/spverificationtracker_test.go b/ledger/spverificationtracker_test.go index a46ba5bde4..1a3fdb3b97 100644 --- a/ledger/spverificationtracker_test.go +++ b/ledger/spverificationtracker_test.go @@ -20,6 +20,8 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" @@ -27,7 +29,6 @@ import ( "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) const defaultStateProofInterval = uint64(256) diff --git a/ledger/store/testing/helpers.go b/ledger/store/testing/helpers.go index f05093c924..5384a06313 100644 --- a/ledger/store/testing/helpers.go +++ b/ledger/store/testing/helpers.go @@ -21,10 +21,11 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/util/db" - "github.com/stretchr/testify/require" ) // DbOpenTest opens a db file for testing purposes. diff --git a/ledger/store/trackerdb/data_test.go b/ledger/store/trackerdb/data_test.go index 3078f0b1a3..b77c7a56d9 100644 --- a/ledger/store/trackerdb/data_test.go +++ b/ledger/store/trackerdb/data_test.go @@ -23,6 +23,9 @@ import ( "reflect" "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" @@ -31,8 +34,6 @@ import ( ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestResourcesDataApp(t *testing.T) { diff --git a/ledger/store/trackerdb/dualdriver/accounts_reader.go b/ledger/store/trackerdb/dualdriver/accounts_reader.go index 27bede02b7..ff3961843a 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_reader.go +++ b/ledger/store/trackerdb/dualdriver/accounts_reader.go @@ -17,9 +17,10 @@ package dualdriver import ( + "github.com/google/go-cmp/cmp" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/store/trackerdb" - "github.com/google/go-cmp/cmp" ) type accountsReader struct { diff --git a/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go b/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go index 5696955516..d469635841 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go +++ b/ledger/store/trackerdb/dualdriver/accounts_reader_ext.go @@ -19,11 +19,12 @@ package dualdriver import ( "context" + "github.com/google/go-cmp/cmp" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" - "github.com/google/go-cmp/cmp" ) type accountsReaderExt struct { diff --git a/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go b/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go index ced4d0aa72..303e0fd334 100644 --- a/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go +++ b/ledger/store/trackerdb/dualdriver/accounts_writer_ext.go @@ -18,6 +18,7 @@ package dualdriver import ( "context" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" diff --git a/ledger/store/trackerdb/dualdriver/dualdriver.go b/ledger/store/trackerdb/dualdriver/dualdriver.go index c88729636d..7cd1e3e2ce 100644 --- a/ledger/store/trackerdb/dualdriver/dualdriver.go +++ b/ledger/store/trackerdb/dualdriver/dualdriver.go @@ -24,12 +24,13 @@ import ( "sync" "time" + "github.com/google/go-cmp/cmp" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/util/db" - "github.com/google/go-cmp/cmp" ) // ErrInconsistentResult is returned when the two stores return different results. diff --git a/ledger/store/trackerdb/dualdriver/online_accounts_reader.go b/ledger/store/trackerdb/dualdriver/online_accounts_reader.go index 8ffcb91334..f30ed07a4d 100644 --- a/ledger/store/trackerdb/dualdriver/online_accounts_reader.go +++ b/ledger/store/trackerdb/dualdriver/online_accounts_reader.go @@ -17,10 +17,11 @@ package dualdriver import ( + "github.com/google/go-cmp/cmp" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" - "github.com/google/go-cmp/cmp" ) type onlineAccountsReader struct { diff --git a/ledger/store/trackerdb/dualdriver/stateproof_reader.go b/ledger/store/trackerdb/dualdriver/stateproof_reader.go index ef15ed2ee6..926b8abb46 100644 --- a/ledger/store/trackerdb/dualdriver/stateproof_reader.go +++ b/ledger/store/trackerdb/dualdriver/stateproof_reader.go @@ -19,10 +19,11 @@ package dualdriver import ( "context" + "github.com/google/go-cmp/cmp" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" - "github.com/google/go-cmp/cmp" ) type stateproofReader struct { diff --git a/ledger/store/trackerdb/dualdriver/stateproof_writer.go b/ledger/store/trackerdb/dualdriver/stateproof_writer.go index 026522bf77..46a5806cb4 100644 --- a/ledger/store/trackerdb/dualdriver/stateproof_writer.go +++ b/ledger/store/trackerdb/dualdriver/stateproof_writer.go @@ -18,6 +18,7 @@ package dualdriver import ( "context" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" diff --git a/ledger/store/trackerdb/generickv/init_accounts.go b/ledger/store/trackerdb/generickv/init_accounts.go index e38b77093a..32a2d20407 100644 --- a/ledger/store/trackerdb/generickv/init_accounts.go +++ b/ledger/store/trackerdb/generickv/init_accounts.go @@ -20,10 +20,11 @@ import ( "context" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) type dbForInit interface { diff --git a/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go b/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go index 341457e024..2fe5d1301a 100644 --- a/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go +++ b/ledger/store/trackerdb/pebbledbdriver/pebbledriver.go @@ -24,14 +24,15 @@ import ( "runtime" "time" + "github.com/cockroachdb/pebble" + "github.com/cockroachdb/pebble/bloom" + "github.com/cockroachdb/pebble/vfs" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/ledger/store/trackerdb/generickv" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/util/db" - "github.com/cockroachdb/pebble" - "github.com/cockroachdb/pebble/bloom" - "github.com/cockroachdb/pebble/vfs" ) const ( diff --git a/ledger/store/trackerdb/pebbledbdriver/testing.go b/ledger/store/trackerdb/pebbledbdriver/testing.go index 57483aa1b7..6b694ce5c6 100644 --- a/ledger/store/trackerdb/pebbledbdriver/testing.go +++ b/ledger/store/trackerdb/pebbledbdriver/testing.go @@ -20,11 +20,12 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) // OpenForTesting opens a sqlite db file for testing purposes. diff --git a/ledger/store/trackerdb/sqlitedriver/accountsV2.go b/ledger/store/trackerdb/sqlitedriver/accountsV2.go index d8eae963ae..97777ae994 100644 --- a/ledger/store/trackerdb/sqlitedriver/accountsV2.go +++ b/ledger/store/trackerdb/sqlitedriver/accountsV2.go @@ -24,13 +24,14 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" - "github.com/stretchr/testify/require" ) type accountsV2Reader struct { diff --git a/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go b/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go index d3c6dad6e6..d6ba02dcb8 100644 --- a/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go +++ b/ledger/store/trackerdb/sqlitedriver/accountsV2_test.go @@ -19,8 +19,9 @@ package sqlitedriver import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestRowidsToChunkedArgs(t *testing.T) { diff --git a/ledger/store/trackerdb/sqlitedriver/catchpoint.go b/ledger/store/trackerdb/sqlitedriver/catchpoint.go index f3d8242af7..8a986a3b9f 100644 --- a/ledger/store/trackerdb/sqlitedriver/catchpoint.go +++ b/ledger/store/trackerdb/sqlitedriver/catchpoint.go @@ -23,13 +23,14 @@ import ( "fmt" "time" + "github.com/mattn/go-sqlite3" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" - "github.com/mattn/go-sqlite3" ) type catchpointReader struct { diff --git a/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go b/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go index aa5c54137c..600fbd7331 100644 --- a/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go +++ b/ledger/store/trackerdb/sqlitedriver/catchpoint_test.go @@ -21,12 +21,13 @@ import ( "crypto/rand" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" storetesting "github.com/algorand/go-algorand/ledger/store/testing" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // Test functions operating on catchpointfirststageinfo table. diff --git a/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go b/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go index 79861c74d6..3266e84f50 100644 --- a/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go +++ b/ledger/store/trackerdb/sqlitedriver/encodedAccountsIter.go @@ -20,11 +20,12 @@ import ( "context" "database/sql" + "github.com/algorand/msgp/msgp" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/util/db" - "github.com/algorand/msgp/msgp" ) // encodedAccountsBatchIter allows us to iterate over the accounts data stored in the accountbase table. diff --git a/ledger/store/trackerdb/sqlitedriver/schema.go b/ledger/store/trackerdb/sqlitedriver/schema.go index be2276ff6f..92b957b039 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema.go +++ b/ledger/store/trackerdb/sqlitedriver/schema.go @@ -25,6 +25,8 @@ import ( "fmt" "time" + "github.com/mattn/go-sqlite3" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto/merklesignature" "github.com/algorand/go-algorand/crypto/merkletrie" @@ -35,7 +37,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" - "github.com/mattn/go-sqlite3" ) var accountsSchema = []string{ diff --git a/ledger/store/trackerdb/sqlitedriver/schema_test.go b/ledger/store/trackerdb/sqlitedriver/schema_test.go index 683d0932f6..f1b46ab168 100644 --- a/ledger/store/trackerdb/sqlitedriver/schema_test.go +++ b/ledger/store/trackerdb/sqlitedriver/schema_test.go @@ -23,6 +23,8 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -35,7 +37,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" - "github.com/stretchr/testify/require" ) func TestAccountsReencoding(t *testing.T) { diff --git a/ledger/store/trackerdb/sqlitedriver/sql.go b/ledger/store/trackerdb/sqlitedriver/sql.go index f88adb7611..eeb298690f 100644 --- a/ledger/store/trackerdb/sqlitedriver/sql.go +++ b/ledger/store/trackerdb/sqlitedriver/sql.go @@ -19,6 +19,7 @@ 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" diff --git a/ledger/store/trackerdb/sqlitedriver/sql_test.go b/ledger/store/trackerdb/sqlitedriver/sql_test.go index ac00b397d9..f1b1ac3299 100644 --- a/ledger/store/trackerdb/sqlitedriver/sql_test.go +++ b/ledger/store/trackerdb/sqlitedriver/sql_test.go @@ -21,13 +21,14 @@ import ( "database/sql" "testing" + "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" storetesting "github.com/algorand/go-algorand/ledger/store/testing" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/mattn/go-sqlite3" - "github.com/stretchr/testify/require" ) func TestKeyPrefixIntervalPreprocessing(t *testing.T) { diff --git a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go index 5f8e2c040a..a3d988b849 100644 --- a/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go +++ b/ledger/store/trackerdb/sqlitedriver/sqlitedriver.go @@ -23,13 +23,14 @@ import ( "testing" "time" + "github.com/mattn/go-sqlite3" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/encoded" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" - "github.com/mattn/go-sqlite3" ) type trackerSQLStore struct { diff --git a/ledger/store/trackerdb/sqlitedriver/testing.go b/ledger/store/trackerdb/sqlitedriver/testing.go index 0f7bab6307..33021b3f79 100644 --- a/ledger/store/trackerdb/sqlitedriver/testing.go +++ b/ledger/store/trackerdb/sqlitedriver/testing.go @@ -22,6 +22,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -29,7 +31,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" - "github.com/stretchr/testify/require" ) // OpenForTesting opens a sqlite db file for testing purposes. diff --git a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go index f2b5c79fd2..189c188c4d 100644 --- a/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go +++ b/ledger/store/trackerdb/testsuite/accounts_ext_kv_test.go @@ -19,13 +19,14 @@ package testsuite import ( "context" + "github.com/stretchr/testify/require" + "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/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) func init() { diff --git a/ledger/store/trackerdb/testsuite/accounts_kv_test.go b/ledger/store/trackerdb/testsuite/accounts_kv_test.go index 99c7d00e95..aed556f696 100644 --- a/ledger/store/trackerdb/testsuite/accounts_kv_test.go +++ b/ledger/store/trackerdb/testsuite/accounts_kv_test.go @@ -17,10 +17,11 @@ package testsuite import ( + "github.com/stretchr/testify/require" + "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" ) func init() { diff --git a/ledger/store/trackerdb/testsuite/dbsemantics_test.go b/ledger/store/trackerdb/testsuite/dbsemantics_test.go index 72ea207462..fe12dd4da4 100644 --- a/ledger/store/trackerdb/testsuite/dbsemantics_test.go +++ b/ledger/store/trackerdb/testsuite/dbsemantics_test.go @@ -19,8 +19,9 @@ package testsuite import ( "context" - "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/ledger/store/trackerdb" ) func init() { diff --git a/ledger/store/trackerdb/testsuite/migration_test.go b/ledger/store/trackerdb/testsuite/migration_test.go index aa3060765d..fb6ce4d285 100644 --- a/ledger/store/trackerdb/testsuite/migration_test.go +++ b/ledger/store/trackerdb/testsuite/migration_test.go @@ -19,13 +19,14 @@ package testsuite import ( "context" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) func init() { diff --git a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go index fb65f49b93..b2acba56ab 100644 --- a/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go +++ b/ledger/store/trackerdb/testsuite/onlineaccounts_kv_test.go @@ -17,11 +17,12 @@ package testsuite import ( + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/store/trackerdb" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) func init() { diff --git a/ledger/store/trackerdb/testsuite/pebbledb_test.go b/ledger/store/trackerdb/testsuite/pebbledb_test.go index fd3d42864c..6bfbe59f7f 100644 --- a/ledger/store/trackerdb/testsuite/pebbledb_test.go +++ b/ledger/store/trackerdb/testsuite/pebbledb_test.go @@ -20,10 +20,11 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/ledger/store/trackerdb/pebbledbdriver" "github.com/algorand/go-algorand/logging" - "github.com/stretchr/testify/require" ) func TestPebbleDB(t *testing.T) { diff --git a/ledger/store/trackerdb/testsuite/sqlitedb_test.go b/ledger/store/trackerdb/testsuite/sqlitedb_test.go index 94a6b93634..a145a9507e 100644 --- a/ledger/store/trackerdb/testsuite/sqlitedb_test.go +++ b/ledger/store/trackerdb/testsuite/sqlitedb_test.go @@ -20,10 +20,11 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/ledger/store/trackerdb/sqlitedriver" "github.com/algorand/go-algorand/logging" - "github.com/stretchr/testify/require" ) func TestSqliteDB(t *testing.T) { diff --git a/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go index a211257826..e0d2558693 100644 --- a/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go +++ b/ledger/store/trackerdb/testsuite/stateproofs_kv_test.go @@ -19,10 +19,11 @@ package testsuite import ( "context" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/ledger/store/trackerdb" - "github.com/stretchr/testify/require" ) func init() { diff --git a/ledger/store/trackerdb/testsuite/utils_test.go b/ledger/store/trackerdb/testsuite/utils_test.go index 658c852231..ec59df1daa 100644 --- a/ledger/store/trackerdb/testsuite/utils_test.go +++ b/ledger/store/trackerdb/testsuite/utils_test.go @@ -26,6 +26,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -35,7 +37,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" - "github.com/stretchr/testify/require" ) type customT struct { diff --git a/ledger/testing/consensusRange.go b/ledger/testing/consensusRange.go index 13b5f2f61b..9f52c647bd 100644 --- a/ledger/testing/consensusRange.go +++ b/ledger/testing/consensusRange.go @@ -21,9 +21,10 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/protocol" - "github.com/stretchr/testify/require" ) var consensusByNumber = []protocol.ConsensusVersion{ diff --git a/ledger/testing/consensusRange_test.go b/ledger/testing/consensusRange_test.go index 1b50e4b1ef..d6fa019605 100644 --- a/ledger/testing/consensusRange_test.go +++ b/ledger/testing/consensusRange_test.go @@ -19,10 +19,11 @@ package testing import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // TestReleasedVersion ensures that the necessary tidying is done when a new diff --git a/ledger/testing/randomAccounts.go b/ledger/testing/randomAccounts.go index ee5a5a6f66..af86364236 100644 --- a/ledger/testing/randomAccounts.go +++ b/ledger/testing/randomAccounts.go @@ -23,9 +23,8 @@ import ( "github.com/algorand/go-algorand/config/bounds" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/protocol" ) var testPoolAddr = 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} diff --git a/ledger/testing/randomAccounts_test.go b/ledger/testing/randomAccounts_test.go index cadd9aa1cb..334710eb18 100644 --- a/ledger/testing/randomAccounts_test.go +++ b/ledger/testing/randomAccounts_test.go @@ -20,10 +20,11 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/test/reflectionhelpers" - "github.com/stretchr/testify/assert" ) func TestAccounts(t *testing.T) { diff --git a/ledger/tracker.go b/ledger/tracker.go index 3b95df3240..413050c2e5 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -25,6 +25,8 @@ import ( "sync/atomic" "time" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -37,7 +39,6 @@ import ( "github.com/algorand/go-algorand/logging/telemetryspec" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util/db" - "github.com/algorand/go-deadlock" ) // ledgerTracker defines part of the API for any state machine that diff --git a/ledger/txnbench_test.go b/ledger/txnbench_test.go index 99d854998a..53a08a101a 100644 --- a/ledger/txnbench_test.go +++ b/ledger/txnbench_test.go @@ -22,13 +22,14 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "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/protocol" - "github.com/stretchr/testify/require" ) // BenchmarkTxnTypes compares the execution time of various txn types diff --git a/ledger/voters_test.go b/ledger/voters_test.go index 56a7f61fc8..836ac2e453 100644 --- a/ledger/voters_test.go +++ b/ledger/voters_test.go @@ -20,6 +20,8 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -29,7 +31,6 @@ import ( ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func addBlockToAccountsUpdate(t *testing.T, blk bookkeeping.Block, ml *mockLedgerForTracker) { diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 876c0778ff..da6106e1e5 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -24,20 +24,19 @@ import ( "path/filepath" "time" - 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/config" "github.com/algorand/go-algorand/crypto" + algodclient "github.com/algorand/go-algorand/daemon/algod/api/client" + v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/daemon/algod/api/spec/common" modelV2 "github.com/algorand/go-algorand/daemon/algod/api/spec/v2" + kmdclient "github.com/algorand/go-algorand/daemon/kmd/client" "github.com/algorand/go-algorand/daemon/kmd/lib/kmdapi" "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/ledgercore" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/util" @@ -1196,20 +1195,22 @@ func MakeDryrunStateGenerated(client Client, txnOrStxnOrSlice interface{}, other apps := []basics.AppIndex{tx.ApplicationID} apps = append(apps, tx.ForeignApps...) for _, appIdx := range apps { - var appParams model.ApplicationParams + var appParams *model.ApplicationParams if appIdx == 0 { // if it is an app create txn then use params from the txn - appParams.ApprovalProgram = tx.ApprovalProgram - appParams.ClearStateProgram = tx.ClearStateProgram - appParams.GlobalStateSchema = &model.ApplicationStateSchema{ - NumUint: tx.GlobalStateSchema.NumUint, - NumByteSlice: tx.GlobalStateSchema.NumByteSlice, - } - appParams.LocalStateSchema = &model.ApplicationStateSchema{ - NumUint: tx.LocalStateSchema.NumUint, - NumByteSlice: tx.LocalStateSchema.NumByteSlice, + appParams = &model.ApplicationParams{ + ApprovalProgram: tx.ApprovalProgram, + ClearStateProgram: tx.ClearStateProgram, + GlobalStateSchema: &model.ApplicationStateSchema{ + NumUint: tx.GlobalStateSchema.NumUint, + NumByteSlice: tx.GlobalStateSchema.NumByteSlice, + }, + LocalStateSchema: &model.ApplicationStateSchema{ + NumUint: tx.LocalStateSchema.NumUint, + NumByteSlice: tx.LocalStateSchema.NumByteSlice, + }, + Creator: tx.Sender.String(), } - appParams.Creator = tx.Sender.String() // zero is not acceptable by ledger in dryrun/debugger appIdx = defaultAppIdx } else { diff --git a/libgoal/libgoal_test.go b/libgoal/libgoal_test.go index 9a008d9bf2..6fd3261a7c 100644 --- a/libgoal/libgoal_test.go +++ b/libgoal/libgoal_test.go @@ -20,12 +20,13 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestValidRounds(t *testing.T) { diff --git a/libgoal/participation/participation_test.go b/libgoal/participation/participation_test.go index e5f8187dc8..9d6746fb39 100644 --- a/libgoal/participation/participation_test.go +++ b/libgoal/participation/participation_test.go @@ -17,13 +17,13 @@ package participation import ( - "github.com/algorand/go-algorand/data/account" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/test/partitiontest" + "testing" "github.com/stretchr/testify/require" - "testing" + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/test/partitiontest" ) func TestGenParticipationKeysTo_Install(t *testing.T) { diff --git a/logging/cyclicWriter.go b/logging/cyclicWriter.go index 45f40718a5..eb8b6deb8a 100644 --- a/logging/cyclicWriter.go +++ b/logging/cyclicWriter.go @@ -26,8 +26,9 @@ import ( "text/template" "time" - "github.com/algorand/go-algorand/util" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/util" ) // CyclicFileWriter implements the io.Writer interface and wraps an underlying file. diff --git a/logging/cyclicWriter_test.go b/logging/cyclicWriter_test.go index 7c16e730b7..f5b16caa9d 100644 --- a/logging/cyclicWriter_test.go +++ b/logging/cyclicWriter_test.go @@ -24,8 +24,9 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func testCyclicWrite(t *testing.T, liveFileName, archiveFileName string) { diff --git a/logging/logBuffer_test.go b/logging/logBuffer_test.go index cb448d40ba..c378b93a26 100644 --- a/logging/logBuffer_test.go +++ b/logging/logBuffer_test.go @@ -22,8 +22,9 @@ import ( "io" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type logBufferTestFixture struct { diff --git a/logging/log_test.go b/logging/log_test.go index 4df219f26e..b75c7eacf9 100644 --- a/logging/log_test.go +++ b/logging/log_test.go @@ -22,8 +22,9 @@ import ( "sync/atomic" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) /* diff --git a/logging/telemetryCommon.go b/logging/telemetryCommon.go index a7d087d1aa..695acdee8d 100644 --- a/logging/telemetryCommon.go +++ b/logging/telemetryCommon.go @@ -20,8 +20,9 @@ import ( "context" "sync" - "github.com/algorand/go-deadlock" "github.com/sirupsen/logrus" + + "github.com/algorand/go-deadlock" ) type telemetryHook interface { diff --git a/logging/telemetryConfig_test.go b/logging/telemetryConfig_test.go index b99415e5ae..3eb44fe725 100644 --- a/logging/telemetryConfig_test.go +++ b/logging/telemetryConfig_test.go @@ -22,8 +22,9 @@ import ( "path/filepath" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func Test_loadTelemetryConfig(t *testing.T) { diff --git a/netdeploy/remote/bootstrappedNetwork_test.go b/netdeploy/remote/bootstrappedNetwork_test.go index b0761c4895..316acce273 100644 --- a/netdeploy/remote/bootstrappedNetwork_test.go +++ b/netdeploy/remote/bootstrappedNetwork_test.go @@ -20,8 +20,9 @@ import ( "path/filepath" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestLoadBootstrappedData(t *testing.T) { diff --git a/network/addr/addr.go b/network/addr/addr.go index 53db9e30b8..68dbd5cbbb 100644 --- a/network/addr/addr.go +++ b/network/addr/addr.go @@ -18,6 +18,7 @@ package addr import ( "errors" + "fmt" "net/url" "regexp" "strings" @@ -29,6 +30,8 @@ var errURLNoHost = errors.New("could not parse a host from url") var errURLColonHost = errors.New("host name starts with a colon") +var errMultiaddrParse = errors.New("failed to parse multiaddr") + // HostColonPortPattern matches "^[-a-zA-Z0-9.]+:\\d+$" e.g. "foo.com.:1234" var HostColonPortPattern = regexp.MustCompile(`^[-a-zA-Z0-9.]+:\d+$`) @@ -78,7 +81,10 @@ func IsMultiaddr(addr string) bool { 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 + if err != nil { + return "", fmt.Errorf("%w: %v", errMultiaddrParse, err) + } + return addr, nil } url, err := ParseHostOrURL(addr) if err != nil { diff --git a/network/addr/addr_test.go b/network/addr/addr_test.go index 9bf6a2ce9f..f725606983 100644 --- a/network/addr/addr_test.go +++ b/network/addr/addr_test.go @@ -20,8 +20,9 @@ import ( "net/url" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type urlCase struct { @@ -114,14 +115,18 @@ func TestParseHostURLOrMultiaddr(t *testing.T) { "/ip4/192.255.2.8/tcp/8180/ws", } - badMultiAddrs := []string{ - "/ip4/256.256.256.256/tcp/8080", // Invalid IPv4 address. - "/ip4/127.0.0.1/abc/8080", // abc is not a valid protocol. - "/ip4/127.0.0.1/tcp/abc", // Port is not a valid number. - "/unix", // Unix protocol without a path is invalid. - "/ip4/127.0.0.1/tcp", // Missing a port after tcp - "/p2p/invalidPeerID", // Invalid peer ID after p2p. - "ip4/127.0.0.1/tcp/8080", // Missing starting /. + badMultiAddrs := []struct { + addr string + err error + }{ + {"/ip4/256.256.256.256/tcp/8080", errMultiaddrParse}, // Invalid IPv4 address. + {"/ip4/127.0.0.1/abc/8080", errMultiaddrParse}, // abc is not a valid protocol. + {"/ip4/127.0.0.1/tcp/abc", errMultiaddrParse}, // Port is not a valid number. + {"/unix", errMultiaddrParse}, // Unix protocol without a path is invalid. + {"/ip4/127.0.0.1/tcp", errMultiaddrParse}, // Missing a port after tcp + {"/p2p/invalidPeerID", errMultiaddrParse}, // Invalid peer ID after p2p. + {"ip4/127.0.0.1/tcp/8080", errURLNoHost}, // Missing starting / - parsed as URL. + {":1234", errURLColonHost}, // Host starts with colon (not IPv6). } for _, addr := range validMultiAddrs { @@ -133,11 +138,11 @@ func TestParseHostURLOrMultiaddr(t *testing.T) { }) } - for _, addr := range badMultiAddrs { - t.Run(addr, func(t *testing.T) { - _, err := ParseHostOrURLOrMultiaddr(addr) - require.Error(t, err) - require.False(t, IsMultiaddr(addr)) + for _, tc := range badMultiAddrs { + t.Run(tc.addr, func(t *testing.T) { + _, err := ParseHostOrURLOrMultiaddr(tc.addr) + require.ErrorIs(t, err, tc.err) + require.False(t, IsMultiaddr(tc.addr)) }) } diff --git a/network/hybridNetwork_test.go b/network/hybridNetwork_test.go index d15ca20066..7d975555e3 100644 --- a/network/hybridNetwork_test.go +++ b/network/hybridNetwork_test.go @@ -21,11 +21,12 @@ import ( "testing" "time" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + "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. diff --git a/network/limitcaller/rateLimitingTransport_test.go b/network/limitcaller/rateLimitingTransport_test.go index 6e7e007f20..fb8955b34d 100644 --- a/network/limitcaller/rateLimitingTransport_test.go +++ b/network/limitcaller/rateLimitingTransport_test.go @@ -21,8 +21,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type ctStore struct { diff --git a/network/limited_reader_slurper_test.go b/network/limited_reader_slurper_test.go index 7e02dbb323..55c535f0ca 100644 --- a/network/limited_reader_slurper_test.go +++ b/network/limited_reader_slurper_test.go @@ -190,7 +190,7 @@ func TestLimitedReaderSlurperPerMessageMaxSize(t *testing.T) { b = make([]byte, dataSize) crypto.RandBytes(b[:]) err := slurper.Read(bytes.NewBuffer(b)) - require.Error(t, err) + require.ErrorIs(t, err, ErrIncomingMsgTooLarge) } } } diff --git a/network/mesh.go b/network/mesh.go index c755f72e20..4f21ed329e 100644 --- a/network/mesh.go +++ b/network/mesh.go @@ -23,9 +23,10 @@ import ( "sync" "time" + "github.com/libp2p/go-libp2p/p2p/discovery/backoff" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network/p2p" - "github.com/libp2p/go-libp2p/p2p/discovery/backoff" ) const meshThreadInterval = time.Minute diff --git a/network/mesh_test.go b/network/mesh_test.go index b2f8a76d7c..b929e7347c 100644 --- a/network/mesh_test.go +++ b/network/mesh_test.go @@ -23,16 +23,17 @@ import ( "testing" "time" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network/limitcaller" p2piface "github.com/algorand/go-algorand/network/p2p" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/require" ) // mockP2PService implements p2p.Service and counts DialPeersUntilTargetCount invocations. @@ -46,6 +47,7 @@ func (m *mockP2PService) IDSigner() *p2piface.PeerIDChallengeSigner { return nil func (m *mockP2PService) AddrInfo() peer.AddrInfo { return peer.AddrInfo{} } func (m *mockP2PService) NetworkNotify(network.Notifiee) {} func (m *mockP2PService) NetworkStopNotify(network.Notifiee) {} +func (m *mockP2PService) UnprotectPeer(peer.ID) {} func (m *mockP2PService) DialPeersUntilTargetCount(int) bool { m.dialCount.Add(1); return true } func (m *mockP2PService) ClosePeer(peer.ID) error { return nil } func (m *mockP2PService) Conns() []network.Conn { return nil } diff --git a/network/msgCompressor.go b/network/msgCompressor.go index 0aa79538d9..b0d4c7ec75 100644 --- a/network/msgCompressor.go +++ b/network/msgCompressor.go @@ -18,6 +18,7 @@ package network import ( "bytes" + "errors" "fmt" "io" "sync/atomic" @@ -219,7 +220,11 @@ func (c *wsPeerMsgCodec) decompress(tag protocol.Tag, data []byte) ([]byte, erro if c.avdec.enabled { res, err := c.avdec.convert(data) if err != nil { - c.log.Warnf("peer %s vote decompress error: %v", c.origin, err) + if errors.Is(err, vpack.ErrLikelyUncompressed) { + // allow uncompressed AV to pass through without logging an error + } else { + c.log.Warnf("peer %s vote decompress error: %v", c.origin, err) + } // fall back to original data return data, nil } diff --git a/network/msgCompressor_test.go b/network/msgCompressor_test.go index 76717b4914..9372b80258 100644 --- a/network/msgCompressor_test.go +++ b/network/msgCompressor_test.go @@ -24,15 +24,16 @@ import ( "time" "github.com/DataDog/zstd" + "github.com/libp2p/go-libp2p/core/peer" + "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/logging" "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var ( @@ -79,7 +80,7 @@ func TestZstdDecompress(t *testing.T) { compressed, err = zstd.Compress(nil, msg) require.NoError(t, err) decompressed, err = d.convert(compressed) - require.Error(t, err) + require.ErrorContains(t, err, `proposal data is too large: 20971530`) require.Nil(t, decompressed) } diff --git a/network/netidentity.go b/network/netidentity.go index 317f7f0017..02555fd3c4 100644 --- a/network/netidentity.go +++ b/network/netidentity.go @@ -21,9 +21,10 @@ import ( "fmt" "net/http" + "github.com/algorand/go-deadlock" + "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" diff --git a/network/netidentity_test.go b/network/netidentity_test.go index 396defde0d..16625699ad 100644 --- a/network/netidentity_test.go +++ b/network/netidentity_test.go @@ -21,10 +21,11 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // if the scheme has a dedup name, attach to headers. otherwise, don't @@ -88,7 +89,8 @@ func TestIdentityChallengeSchemeVerifyRequestAndAttachResponse(t *testing.T) { require.Empty(t, r.Get(IdentityChallengeHeader)) require.Empty(t, chal) require.Empty(t, key) - require.Error(t, err) + var corruptInputErr base64.CorruptInputError + require.ErrorAs(t, err, &corruptInputErr) // happy path: response should be attached here h = http.Header{} @@ -198,7 +200,7 @@ func TestIdentityChallengeSchemeBadSignature(t *testing.T) { require.Empty(t, r.Get(IdentityChallengeHeader)) require.Empty(t, respChal) require.Empty(t, key) - require.Error(t, err) + require.ErrorContains(t, err, `identity challenge incorrectly signed`) } // TestIdentityChallengeSchemeBadPayload tests that the scheme will @@ -216,7 +218,8 @@ func TestIdentityChallengeSchemeBadPayload(t *testing.T) { require.Empty(t, r.Get(IdentityChallengeHeader)) require.Empty(t, respChal) require.Empty(t, key) - require.Error(t, err) + var corruptInputErr base64.CorruptInputError + require.ErrorAs(t, err, &corruptInputErr) } // TestIdentityChallengeSchemeBadResponseSignature tests that the scheme will @@ -247,7 +250,7 @@ func TestIdentityChallengeSchemeBadResponseSignature(t *testing.T) { key2, verificationMsg, err := i.VerifyResponse(r, origChal) require.Empty(t, key2) require.Empty(t, verificationMsg) - require.Error(t, err) + require.ErrorContains(t, err, `challenge response incorrectly signed`) } // TestIdentityChallengeSchemeBadResponsePayload tests that the scheme will @@ -269,7 +272,8 @@ func TestIdentityChallengeSchemeBadResponsePayload(t *testing.T) { key2, verificationMsg, err := i.VerifyResponse(r, origChal) require.Empty(t, key2) require.Empty(t, verificationMsg) - require.Error(t, err) + var corruptInputErr base64.CorruptInputError + require.ErrorAs(t, err, &corruptInputErr) } // TestIdentityChallengeSchemeWrongChallenge the scheme will @@ -295,7 +299,7 @@ func TestIdentityChallengeSchemeWrongChallenge(t *testing.T) { key2, verificationMsg, err := i.VerifyResponse(r, newIdentityChallengeValue()) require.Empty(t, key2) require.Empty(t, verificationMsg) - require.Error(t, err) + require.ErrorContains(t, err, `challenge response did not contain originally issued challenge value`) } func TestNewIdentityTracker(t *testing.T) { diff --git a/network/p2p/http.go b/network/p2p/http.go index ae3468aa23..f2373e135b 100644 --- a/network/p2p/http.go +++ b/network/p2p/http.go @@ -25,8 +25,6 @@ import ( "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" @@ -36,6 +34,9 @@ import ( bhost "github.com/libp2p/go-libp2p/p2p/host/basic" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" "github.com/multiformats/go-multiaddr" + + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/limitcaller" ) // algorandP2pHTTPProtocol defines a libp2p protocol name for algorand's http over p2p messages diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 44677ecb14..de420a0837 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -19,22 +19,16 @@ package p2p import ( "context" "fmt" + "math" "net" "net/http" "runtime" "strings" "time" - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/network/limitcaller" - 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" + connmgrcore "github.com/libp2p/go-libp2p/core/connmgr" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" @@ -42,10 +36,20 @@ import ( "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/net/connmgr" "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" + + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/limitcaller" + pstore "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/network/phonebook" + "github.com/algorand/go-algorand/util/metrics" ) // SubNextCancellable is an abstraction for pubsub.Subscription @@ -66,6 +70,7 @@ type Service interface { DialPeersUntilTargetCount(targetConnCount int) bool ClosePeer(peer.ID) error + UnprotectPeer(peer.ID) Conns() []network.Conn ListPeersForTopic(topic string) []peer.ID @@ -76,15 +81,9 @@ type Service interface { GetHTTPClient(addrInfo *peer.AddrInfo, connTimeStore limitcaller.ConnectionTimeStore, queueingTimeout time.Duration) (*http.Client, error) } -// subset of config.Local needed here -type nodeSubConfig interface { - IsHybridServer() bool -} - // serviceImpl manages integration with libp2p and implements the Service interface type serviceImpl struct { log logging.Logger - subcfg nodeSubConfig listenAddr string host host.Host streams *streamManager @@ -105,7 +104,7 @@ const AlgorandWsProtocolV22 = "/algorand-ws/2.2.0" const dialTimeout = 30 * time.Second -const psmdkDialed = "dialed" +const cnmgrTag = "algorand-ws-mesh" // MakeHost creates a libp2p host but does not start listening. // Use host.Network().Listen() on the returned address to start listening. @@ -142,6 +141,9 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. logging.Base().Debug("p2p NetAddress is not set, not listening") listenAddr = "" } + if cfg.IncomingConnectionsLimit == 0 { + listenAddr = "" + } var enableMetrics = func(cfg *libp2p.Config) error { cfg.DisableMetrics = false; return nil } metrics.DefaultRegistry().Register(&metrics.PrometheusDefaultMetrics) @@ -152,7 +154,12 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. addrFactory = addressFilter } - rm, err := configureResourceManager(cfg) + connLimits := deriveConnLimits(cfg) + rm, err := configureResourceManager(connLimits) + if err != nil { + return nil, "", err + } + cm, err := configureConnManager(connLimits) if err != nil { return nil, "", err } @@ -167,21 +174,76 @@ func MakeHost(cfg config.Local, datadir string, pstore *pstore.PeerStore) (host. libp2p.Security(noise.ID, noise.New), enableMetrics, libp2p.ResourceManager(rm), + libp2p.ConnectionManager(cm), libp2p.AddrsFactory(addrFactory), ) return host, listenAddr, err } -func configureResourceManager(cfg config.Local) (network.ResourceManager, error) { +// connLimitConfig holds derived connection limits for both the connmgr and rcmgr. +type connLimitConfig struct { + connMgrLow int + connMgrHigh int + rcmgrConns int + rcmgrConnsInbound int + rcmgrConnsOutbound int +} + +// deriveConnLimits computes connection manager and resource manager limits +// from the node configuration. Listen servers use IncomingConnectionsLimit; +// client nodes use tighter limits based on GossipFanout. +func deriveConnLimits(cfg config.Local) connLimitConfig { + var low, high, rcmgrConns, rcmgrConnsInbound, rcmgrConnsOutbound int + rcmgrConnsOutbound = cfg.GossipFanout * 3 + if cfg.EnableDHTProviders { + rcmgrConnsOutbound += cfg.GossipFanout * 3 + } + if cfg.IsListenServer() { + if cfg.IncomingConnectionsLimit < 0 { + rcmgrConns = math.MaxInt + rcmgrConnsInbound = math.MaxInt + high = 0 + low = 0 + } else { + rcmgrConns = rcmgrConnsOutbound + cfg.IncomingConnectionsLimit + rcmgrConnsInbound = cfg.IncomingConnectionsLimit + high = rcmgrConns + low = high * 96 / 100 + } + } else { + rcmgrConns = rcmgrConnsOutbound + cfg.GossipFanout*3 + high = rcmgrConnsOutbound + low = cfg.GossipFanout * 2 + } + + return connLimitConfig{ + connMgrLow: low, + connMgrHigh: high, + rcmgrConns: rcmgrConns, + rcmgrConnsInbound: rcmgrConnsInbound, + rcmgrConnsOutbound: rcmgrConnsOutbound, + } +} + +func configureResourceManager(limits connLimitConfig) (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() + systemLimits := rcmgr.ResourceLimits{} + if limits.rcmgrConns > 0 { + systemLimits.Conns = rcmgr.LimitVal(limits.rcmgrConns) + } + if limits.rcmgrConnsInbound > 0 { + systemLimits.ConnsInbound = rcmgr.LimitVal(limits.rcmgrConnsInbound) + } + if limits.rcmgrConnsOutbound > 0 { + systemLimits.ConnsOutbound = rcmgr.LimitVal(limits.rcmgrConnsOutbound) + } + limitConfig := rcmgr.PartialLimitConfig{ - System: rcmgr.ResourceLimits{ - Conns: rcmgr.LimitVal(cfg.IncomingConnectionsLimit), - }, + System: systemLimits, // Everything else is default. The exact values will come from `scaledDefaultLimits` above. } limiter := rcmgr.NewFixedLimiter(limitConfig.Build(scaledDefaultLimits)) @@ -189,6 +251,12 @@ func configureResourceManager(cfg config.Local) (network.ResourceManager, error) return rm, err } +func configureConnManager(limits connLimitConfig) (connmgrcore.ConnManager, error) { + // connMgrLow = 0 and connMgrHigh = 0 mean disabled conns trimming + return connmgr.NewConnManager(limits.connMgrLow, limits.connMgrHigh, + connmgr.WithGracePeriod(20*time.Second)) +} + // StreamHandlerPair is a struct that contains a protocol ID and a StreamHandler type StreamHandlerPair struct { ProtoID protocol.ID @@ -198,55 +266,22 @@ type StreamHandlerPair struct { // StreamHandlers is an ordered list of StreamHandlerPair type StreamHandlers []StreamHandlerPair -// PubSubOption is a function that modifies the pubsub options -type PubSubOption func(opts *[]pubsub.Option) - -// DisablePubSubPeerExchange disables PX (peer exchange) in pubsub -func DisablePubSubPeerExchange() PubSubOption { - return func(opts *[]pubsub.Option) { - *opts = append(*opts, pubsub.WithPeerExchange(false)) - } -} - -// SetPubSubMetricsTracer sets a pubsub.RawTracer for metrics collection -func SetPubSubMetricsTracer(metricsTracer pubsub.RawTracer) PubSubOption { - return func(opts *[]pubsub.Option) { - *opts = append(*opts, pubsub.WithRawTracer(metricsTracer)) - } -} - -// SetPubSubPeerFilter sets a pubsub.PeerFilter for peers filtering out -func SetPubSubPeerFilter(filter func(checker pstore.RoleChecker, pid peer.ID) bool, checker pstore.RoleChecker) PubSubOption { - return func(opts *[]pubsub.Option) { - f := func(pid peer.ID, topic string) bool { - return filter(checker, pid) - } - *opts = append(*opts, pubsub.WithPeerFilter(f)) - } -} - // MakeService creates a P2P service instance func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandlers StreamHandlers, pubsubOptions ...PubSubOption) (*serviceImpl, error) { - sm := makeStreamManager(ctx, log, cfg, h, wsStreamHandlers, cfg.EnableGossipService) + sm := makeStreamManager(ctx, log, h, wsStreamHandlers, cfg.EnableGossipService) h.Network().Notify(sm) for _, pair := range wsStreamHandlers { h.SetStreamHandler(pair.ProtoID, sm.streamHandler) } - pubsubOpts := []pubsub.Option{} - for _, opt := range pubsubOptions { - opt(&pubsubOpts) - } - - ps, err := makePubSub(ctx, h, cfg.GossipFanout, pubsubOpts...) + ps, err := makePubSub(ctx, h, cfg.GossipFanout, pubsubOptions...) if err != nil { return nil, err } return &serviceImpl{ log: log, - subcfg: cfg, listenAddr: listenAddr, host: h, streams: sm, @@ -296,13 +331,8 @@ func (s *serviceImpl) DialPeersUntilTargetCount(targetConnCount int) bool { var numOutgoingConns int for _, conn := range conns { if conn.Stat().Direction == network.DirOutbound { - if s.subcfg.IsHybridServer() { - remotePeer := conn.RemotePeer() - val, err := s.host.Peerstore().Get(remotePeer, psmdkDialed) - if err == nil && val != nil && val.(bool) { - numOutgoingConns++ - } - } else { + remotePeer := conn.RemotePeer() + if s.host.ConnManager().IsProtected(remotePeer, cnmgrTag) { numOutgoingConns++ } } @@ -313,8 +343,18 @@ func (s *serviceImpl) DialPeersUntilTargetCount(targetConnCount int) bool { if numOutgoingConns >= targetConnCount { return numOutgoingConns > preExistingConns } - // if we are already connected to this peer, skip it - if len(s.host.Network().ConnsToPeer(peerInfo.ID)) > 0 { + // if we are already connected to this peer, ensure it's properly handled + if conns := s.host.Network().ConnsToPeer(peerInfo.ID); len(conns) > 0 { + if !s.host.ConnManager().IsProtected(peerInfo.ID, cnmgrTag) { + // connection was established by DHT/pubsub before the mesh thread + // could protect it, so handleConnected skipped stream creation. + // protect and re-trigger stream setup now. + s.host.ConnManager().Protect(peerInfo.ID, cnmgrTag) + go s.streams.handleConnected(conns[0]) + if conns[0].Stat().Direction == network.DirOutbound { + numOutgoingConns++ + } + } continue } err := s.dialNode(context.Background(), peerInfo) // leaving the calls as blocking for now, to not over-connect beyond fanout @@ -335,12 +375,13 @@ func (s *serviceImpl) dialNode(ctx context.Context, peer *peer.AddrInfo) error { } ctx, cancel := context.WithTimeout(ctx, dialTimeout) defer cancel() - if s.subcfg.IsHybridServer() { - if err := s.host.Peerstore().Put(peer.ID, psmdkDialed, true); err != nil { // mark this peer as explicitly dialed - return err - } + // protect before attempting to connect so that checks in Connected handler have up-to-date information + s.host.ConnManager().Protect(peer.ID, cnmgrTag) + err := s.host.Connect(ctx, *peer) + if err != nil { + s.host.ConnManager().Unprotect(peer.ID, cnmgrTag) } - return s.host.Connect(ctx, *peer) + return err } // AddrInfo returns the peer.AddrInfo for self @@ -365,6 +406,12 @@ func (s *serviceImpl) ClosePeer(peer peer.ID) error { return s.host.Network().ClosePeer(peer) } +// UnprotectPeer removes the protection from a peer, allowing +// the connection manager to trim it if needed. +func (s *serviceImpl) UnprotectPeer(id peer.ID) { + s.host.ConnManager().Unprotect(id, cnmgrTag) +} + // 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 5973b00ea6..9f15616b57 100644 --- a/network/p2p/p2p_test.go +++ b/network/p2p/p2p_test.go @@ -17,9 +17,12 @@ package p2p import ( + "context" "fmt" + "math" "net" "testing" + "time" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/peer" @@ -29,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network/p2p/peerstore" "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-algorand/test/partitiontest" @@ -76,7 +80,7 @@ func TestNetAddressToListenAddress(t *testing.T) { t.Run(fmt.Sprintf("input: %s", test.input), func(t *testing.T) { res, err := netAddressToListenAddress(test.input) if test.err { - require.Error(t, err) + require.ErrorContains(t, err, `invalid netAddress`) } else { require.NoError(t, err) require.Equal(t, test.output, res) @@ -246,18 +250,41 @@ func TestP2PMakeHostAddressFilter(t *testing.T) { } } +func TestP2PServiceStartZeroIncomingDoesNotListen(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.NetAddress = "127.0.0.1:0" + cfg.IncomingConnectionsLimit = 0 + + td := t.TempDir() + pstore, err := peerstore.NewPeerStore(nil, "test") + require.NoError(t, err) + + host, la, err := MakeHost(cfg, td, pstore) + require.NoError(t, err) + + svc, err := MakeService(context.Background(), logging.TestingLog(t), cfg, host, la, StreamHandlers{}) + require.NoError(t, err) + defer svc.Close() + + require.NoError(t, svc.Start()) + require.Empty(t, host.Network().ListenAddresses()) +} + func TestP2PPubSubOptions(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() var opts []pubsub.Option option := DisablePubSubPeerExchange() - option(&opts) + option(&opts, nil) require.Len(t, opts, 1) tracer := &mockRawTracer{} option = SetPubSubMetricsTracer(tracer) - option(&opts) + option(&opts, nil) require.Len(t, opts, 2) filterFunc := func(roleChecker peerstore.RoleChecker, pid peer.ID) bool { @@ -265,8 +292,14 @@ func TestP2PPubSubOptions(t *testing.T) { } checker := &mockRoleChecker{} option = SetPubSubPeerFilter(filterFunc, checker) - option(&opts) + option(&opts, nil) require.Len(t, opts, 3) + + option = SetPubSubHeartbeatInterval(100 * time.Millisecond) + params := &pubsub.GossipSubParams{} + option(&opts, params) + require.Len(t, opts, 3) // SetPubSubHeartbeatInterval does not add to opts but updates params + require.Equal(t, 100*time.Millisecond, params.HeartbeatInterval) } type mockRawTracer struct{} @@ -292,3 +325,122 @@ type mockRoleChecker struct{} func (m *mockRoleChecker) HasRole(pid peer.ID, role phonebook.Role) bool { return true } + +func TestDeriveConnLimits_Server(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.NetAddress = ":4160" + cfg.IncomingConnectionsLimit = 2400 + cfg.GossipFanout = 4 + limits := deriveConnLimits(cfg) + require.Equal(t, 2400+12, limits.rcmgrConns) + require.Equal(t, 2400, limits.rcmgrConnsInbound) + require.Equal(t, 12, limits.rcmgrConnsOutbound) + require.Equal(t, 2412, limits.connMgrHigh) + require.Equal(t, 2315, limits.connMgrLow) // 2412 * 96 / 100 + require.LessOrEqual(t, limits.connMgrHigh, limits.rcmgrConns) +} + +func TestDeriveConnLimits_UnboundedServer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.NetAddress = ":4160" + cfg.IncomingConnectionsLimit = -1 + cfg.GossipFanout = 4 + limits := deriveConnLimits(cfg) + require.Equal(t, math.MaxInt, limits.rcmgrConns) + require.Equal(t, math.MaxInt, limits.rcmgrConnsInbound) + require.Equal(t, 12, limits.rcmgrConnsOutbound) + require.Equal(t, 0, limits.connMgrHigh) + require.Equal(t, 0, limits.connMgrLow) +} + +func TestDeriveConnLimits_DHTProviders(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.NetAddress = ":4160" + cfg.IncomingConnectionsLimit = 2400 + cfg.GossipFanout = 4 + cfg.EnableDHTProviders = true + limits := deriveConnLimits(cfg) + require.Equal(t, 2400+12+12, limits.rcmgrConns) + require.Equal(t, 2400, limits.rcmgrConnsInbound) + require.Equal(t, 24, limits.rcmgrConnsOutbound) + require.Equal(t, 2424, limits.connMgrHigh) + require.Equal(t, 2327, limits.connMgrLow) // 2424 * 96 / 100 +} + +func TestDeriveConnLimits_Client(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.GossipFanout = 4 + limits := deriveConnLimits(cfg) + require.Equal(t, 24, limits.rcmgrConns) // 4 * 6 + require.Equal(t, 0, limits.rcmgrConnsInbound) + require.Equal(t, 12, limits.rcmgrConnsOutbound) // 4 * 3 + require.Equal(t, 12, limits.connMgrHigh) // 4 * 3 + require.Equal(t, 8, limits.connMgrLow) // 4 * 2 + require.LessOrEqual(t, limits.connMgrHigh, limits.rcmgrConns) + + // ensure cfg.IncomingConnectionsLimit = -1 does not affect client limits + cfg.IncomingConnectionsLimit = -1 + newLimits := deriveConnLimits(cfg) + require.Equal(t, limits, newLimits) +} + +func TestDeriveConnLimits_HybridClient(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // Hybrid client: EnableP2PHybridMode but no listen addresses + cfg := config.GetDefaultLocal() + cfg.EnableP2PHybridMode = true + cfg.GossipFanout = 4 + limits := deriveConnLimits(cfg) + require.Equal(t, 24, limits.rcmgrConns) + require.Equal(t, 0, limits.rcmgrConnsInbound) + require.Equal(t, 12, limits.rcmgrConnsOutbound) + require.Equal(t, 12, limits.connMgrHigh) + require.Equal(t, 8, limits.connMgrLow) + require.LessOrEqual(t, limits.connMgrHigh, limits.rcmgrConns) +} + +func TestDeriveConnLimits_HybridServer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.EnableP2PHybridMode = true + cfg.NetAddress = ":4160" + cfg.P2PHybridNetAddress = ":4190" + cfg.IncomingConnectionsLimit = 2400 + limits := deriveConnLimits(cfg) + require.Equal(t, 2412, limits.rcmgrConns) + require.Equal(t, 2400, limits.rcmgrConnsInbound) + require.Equal(t, 12, limits.rcmgrConnsOutbound) + require.Equal(t, 2412, limits.connMgrHigh) + require.Equal(t, 2315, limits.connMgrLow) + require.LessOrEqual(t, limits.connMgrHigh, limits.rcmgrConns) +} + +func TestDeriveConnLimits_ZeroFanout(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + cfg := config.GetDefaultLocal() + cfg.GossipFanout = 0 + limits := deriveConnLimits(cfg) + require.GreaterOrEqual(t, limits.connMgrLow, 0) // zero means zero + require.GreaterOrEqual(t, limits.connMgrHigh, limits.connMgrLow) + require.GreaterOrEqual(t, limits.rcmgrConns, limits.connMgrHigh) + require.Equal(t, 0, limits.rcmgrConnsInbound) + require.Equal(t, 0, limits.rcmgrConnsOutbound) +} diff --git a/network/p2p/peerID.go b/network/p2p/peerID.go index 291de66425..84b535f8e5 100644 --- a/network/p2p/peerID.go +++ b/network/p2p/peerID.go @@ -27,12 +27,12 @@ import ( "os" "path" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "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" - "github.com/libp2p/go-libp2p/core/peer" ) // DefaultPrivKeyPath is the default path inside the node's root directory at which the private key diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go index 3d37334d5d..23a9737fad 100644 --- a/network/p2p/peerstore/peerstore.go +++ b/network/p2p/peerstore/peerstore.go @@ -27,8 +27,9 @@ import ( libp2p "github.com/libp2p/go-libp2p/core/peerstore" mempstore "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoremem" - "github.com/algorand/go-algorand/network/phonebook" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/network/phonebook" ) // when using GetAddresses with getAllAddresses, all the addresses will be retrieved, regardless diff --git a/network/p2p/pubsub.go b/network/p2p/pubsub.go index 8ff628a37b..33b021f1e8 100644 --- a/network/p2p/pubsub.go +++ b/network/p2p/pubsub.go @@ -25,20 +25,43 @@ import ( "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/peer" "golang.org/x/crypto/blake2b" + + pstore "github.com/algorand/go-algorand/network/p2p/peerstore" ) -func init() { - // configure larger overlay parameters - pubsub.GossipSubD = 8 - pubsub.GossipSubDscore = 6 - pubsub.GossipSubDout = 3 - pubsub.GossipSubDlo = 6 - pubsub.GossipSubDhi = 12 - pubsub.GossipSubDlazy = 12 - pubsub.GossipSubDirectConnectInitialDelay = 30 * time.Second - pubsub.GossipSubIWantFollowupTime = 5 * time.Second - pubsub.GossipSubHistoryLength = 10 - pubsub.GossipSubGossipFactor = 0.1 +// PubSubOption is a function that modifies the pubsub options +type PubSubOption func(opts *[]pubsub.Option, params *pubsub.GossipSubParams) + +// DisablePubSubPeerExchange disables PX (peer exchange) in pubsub +func DisablePubSubPeerExchange() PubSubOption { + return func(opts *[]pubsub.Option, _ *pubsub.GossipSubParams) { + *opts = append(*opts, pubsub.WithPeerExchange(false)) + } +} + +// SetPubSubMetricsTracer sets a pubsub.RawTracer for metrics collection +func SetPubSubMetricsTracer(metricsTracer pubsub.RawTracer) PubSubOption { + return func(opts *[]pubsub.Option, _ *pubsub.GossipSubParams) { + *opts = append(*opts, pubsub.WithRawTracer(metricsTracer)) + } +} + +// SetPubSubPeerFilter sets a pubsub.PeerFilter for peers filtering out +func SetPubSubPeerFilter(filter func(checker pstore.RoleChecker, pid peer.ID) bool, checker pstore.RoleChecker) PubSubOption { + return func(opts *[]pubsub.Option, _ *pubsub.GossipSubParams) { + f := func(pid peer.ID, topic string) bool { + return filter(checker, pid) + } + *opts = append(*opts, pubsub.WithPeerFilter(f)) + } +} + +// SetPubSubHeartbeatInterval sets the heartbeat interval in pubsub GossipSubParams +// Note: subsequent call of pubsub.WithGossipSubParams is needed. +func SetPubSubHeartbeatInterval(interval time.Duration) PubSubOption { + return func(opts *[]pubsub.Option, params *pubsub.GossipSubParams) { + params.HeartbeatInterval = interval + } } const ( @@ -57,24 +80,67 @@ const TXTopicName = "algotx01" const incomingThreads = 20 // matches to number wsNetwork workers -// deriveGossipSubParams derives the gossip sub parameters from the cfg.GossipFanout value +// deriveAlgorandGossipSubParams derives the gossip sub parameters from the cfg.GossipFanout value // by using the same proportions as pubsub defaults - see GossipSubD, GossipSubDlo, etc. -func deriveGossipSubParams(numOutgoingConns int) pubsub.GossipSubParams { +func deriveAlgorandGossipSubParams(numOutgoingConns int) pubsub.GossipSubParams { params := pubsub.DefaultGossipSubParams() - params.D = numOutgoingConns - params.Dlo = params.D - 1 - if params.Dlo <= 0 { - params.Dlo = params.D + + // configure larger overlay parameters + // despite the fact D-values are later overridden based on numOutgoingConns, + // we still want to give an idea what values might/should be by default. + params.D = 8 + params.Dscore = 6 + params.Dout = 3 + params.Dlo = 6 + params.Dhi = 12 + params.Dlazy = 12 + params.DirectConnectInitialDelay = 30 * time.Second + params.IWantFollowupTime = 5 * time.Second + params.HistoryLength = 10 + params.GossipFactor = 0.1 + + if numOutgoingConns >= 12 { + // large number of outgoing conns, cap to the defaults above + return params } - params.Dscore = params.D * 2 / 3 - params.Dout = params.D * 1 / 3 + if numOutgoingConns <= 0 { + // no outgoing connections + params.D = 0 + params.Dscore = 0 + params.Dout = 0 + params.Dlo = 0 + params.Dhi = 0 + params.Dlazy = 0 + return params + } + if numOutgoingConns <= 4 { + // use some minimal meaningful values satisfying + // go-libp2p-pubsub constraints like Dout < Dlo && Dout < D/2 + // note, Dout=1 requires D >= 4 so that numOutgoingConns <= 4 implies hardcoded values + params.D = 4 + params.Dscore = 1 + params.Dout = 1 + params.Dlo = 2 + params.Dhi = 4 + params.Dlazy = 4 + return params + } + + // for numOutgoingConns in (4, 12), scale the D parameters proportionally + // to the number of outgoing connections, keeping the same proportions as the defaults. + // + // ratios from the defaults: D/n ~= 2/3, Dlo/D = Dscore/D = 3/4, Dout/D = 3/8, and Dhi = Dlazy = n + params.D = numOutgoingConns - numOutgoingConns/3 + params.Dlo = params.D * 3 / 4 + params.Dscore = params.D * 3 / 4 + params.Dhi = numOutgoingConns + params.Dlazy = numOutgoingConns + params.Dout = params.D * 3 / 8 return params } -func makePubSub(ctx context.Context, host host.Host, numOutgoingConns int, opts ...pubsub.Option) (*pubsub.PubSub, error) { - gossipSubParams := deriveGossipSubParams(numOutgoingConns) +func makePubSub(ctx context.Context, host host.Host, numOutgoingConns int, pubsubOptions ...PubSubOption) (*pubsub.PubSub, error) { options := []pubsub.Option{ - pubsub.WithGossipSubParams(gossipSubParams), pubsub.WithPeerScore(&pubsub.PeerScoreParams{ DecayInterval: pubsub.DefaultDecayInterval, DecayToZero: pubsub.DefaultDecayToZero, @@ -116,7 +182,12 @@ func makePubSub(ctx context.Context, host host.Host, numOutgoingConns int, opts pubsub.WithValidateWorkers(incomingThreads), } - options = append(options, opts...) + gossipSubParams := deriveAlgorandGossipSubParams(numOutgoingConns) + for _, opt := range pubsubOptions { + opt(&options, &gossipSubParams) + } + options = append(options, pubsub.WithGossipSubParams(gossipSubParams)) + return pubsub.NewGossipSub(ctx, host, options...) } diff --git a/network/p2p/pubsub_test.go b/network/p2p/pubsub_test.go index b7cfab9716..18b97e1214 100644 --- a/network/p2p/pubsub_test.go +++ b/network/p2p/pubsub_test.go @@ -19,10 +19,8 @@ package p2p import ( "testing" - pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -30,22 +28,48 @@ func TestPubsub_GossipSubParamsBasic(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - cfg := config.GetDefaultLocal() - - for _, fanout := range []int{4, 8} { - cfg.GossipFanout = fanout + // expected values for n from 5 (calculated) to 12 (max default) + // n, D, Dlo, Dscore, Dout, Dhi, Dlazy + expected := []struct { + n, D, Dlo, Dscore, Dout, Dhi, Dlazy int + }{ + {5, 4, 3, 3, 1, 5, 5}, + {6, 4, 3, 3, 1, 6, 6}, + {7, 5, 3, 3, 1, 7, 7}, + {8, 6, 4, 4, 2, 8, 8}, + {9, 6, 4, 4, 2, 9, 9}, + {10, 7, 5, 5, 2, 10, 10}, + {11, 8, 6, 6, 3, 11, 11}, + {12, 8, 6, 6, 3, 12, 12}, + } - params := deriveGossipSubParams(cfg.GossipFanout) + for _, e := range expected { + p := deriveAlgorandGossipSubParams(e.n) + require.Equal(t, e.D, p.D, "n=%d D", e.n) + require.Equal(t, e.Dlo, p.Dlo, "n=%d Dlo", e.n) + require.Equal(t, e.Dscore, p.Dscore, "n=%d Dscore", e.n) + require.Equal(t, e.Dout, p.Dout, "n=%d Dout", e.n) + require.Equal(t, e.Dhi, p.Dhi, "n=%d Dhi", e.n) + require.Equal(t, e.Dlazy, p.Dlazy, "n=%d Dlazy", e.n) + } +} - require.Equal(t, fanout, params.D) - require.Equal(t, fanout-1, params.Dlo) - require.Equal(t, fanout*2/3, params.Dscore) - require.Equal(t, fanout*1/3, params.Dout) +// Verify libp2p gossipsub validate() constraints +// 1. Dlo <= D <= Dhi +// 2. Dscore <= Dhi +// 3. Dout < Dlo (strict) +// 4. Dout < D/2 (strict, integer division) +func TestPubsub_GossipSubParamsValidateConstraints(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() - // Sanity: other defaults are preserved (not zeroed). Avoid asserting exact values to reduce brittleness. - def := pubsub.DefaultGossipSubParams() - require.Equal(t, def.HeartbeatInitialDelay, params.HeartbeatInitialDelay) - require.Equal(t, def.HistoryLength, params.HistoryLength) + for n := 1; n <= 20; n++ { + p := deriveAlgorandGossipSubParams(n) + require.LessOrEqual(t, p.Dlo, p.D, "n=%d: Dlo <= D", n) + require.LessOrEqual(t, p.D, p.Dhi, "n=%d: D <= Dhi", n) + require.LessOrEqual(t, p.Dscore, p.Dhi, "n=%d: Dscore <= Dhi", n) + require.Less(t, p.Dout, p.Dlo, "n=%d: Dout < Dlo", n) + require.Less(t, p.Dout, p.D/2, "n=%d: Dout < D/2", n) } } @@ -53,21 +77,34 @@ func TestPubsub_GossipSubParamsEdgeCases(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // D = 1 => Dlo must not drop below 1 - cfg := config.GetDefaultLocal() - cfg.GossipFanout = 1 - p := deriveGossipSubParams(cfg.GossipFanout) - require.Equal(t, 1, p.D) - require.Equal(t, 1, p.Dlo) - require.Equal(t, 0, p.Dscore) - require.Equal(t, 0, p.Dout) - - // D = 0 => keep Dlo = D (0) instead of negative - cfg = config.GetDefaultLocal() - cfg.GossipFanout = 0 - p = deriveGossipSubParams(cfg.GossipFanout) + // n = 0: all zeros + p := deriveAlgorandGossipSubParams(0) require.Equal(t, 0, p.D) require.Equal(t, 0, p.Dlo) require.Equal(t, 0, p.Dscore) require.Equal(t, 0, p.Dout) + require.Equal(t, 0, p.Dhi) + require.Equal(t, 0, p.Dlazy) + + // n = 1..4: low bound + for n := 1; n <= 4; n++ { + p = deriveAlgorandGossipSubParams(n) + require.Equal(t, 4, p.D) + require.Equal(t, 2, p.Dlo) + require.Equal(t, 1, p.Dscore) + require.Equal(t, 1, p.Dout) + require.Equal(t, 4, p.Dhi) + require.Equal(t, 4, p.Dlazy) + } + + // n >= 12: capped to defaults + for n := 12; n <= 20; n++ { + p = deriveAlgorandGossipSubParams(n) + require.Equal(t, 8, p.D) + require.Equal(t, 6, p.Dlo) + require.Equal(t, 6, p.Dscore) + require.Equal(t, 3, p.Dout) + require.Equal(t, 12, p.Dhi) + require.Equal(t, 12, p.Dlazy) + } } diff --git a/network/p2p/streams.go b/network/p2p/streams.go index 7f9cf0260f..97282c7ac8 100644 --- a/network/p2p/streams.go +++ b/network/p2p/streams.go @@ -21,20 +21,21 @@ import ( "fmt" "io" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-deadlock" "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/protocol" "github.com/multiformats/go-multiaddr" + + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/logging" ) // streamManager implements network.Notifiee to create and manage streams for use with non-gossipsub protocols. type streamManager struct { ctx context.Context log logging.Logger - cfg nodeSubConfig host host.Host handlers StreamHandlers allowIncomingGossip bool @@ -44,13 +45,12 @@ 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) +type StreamHandler func(ctx context.Context, pid peer.ID, s network.Stream, incoming bool) error -func makeStreamManager(ctx context.Context, log logging.Logger, cfg nodeSubConfig, h host.Host, handlers StreamHandlers, allowIncomingGossip bool) *streamManager { +func makeStreamManager(ctx context.Context, log logging.Logger, h host.Host, handlers StreamHandlers, allowIncomingGossip bool) *streamManager { return &streamManager{ ctx: ctx, log: log, - cfg: cfg, host: h, handlers: handlers, allowIncomingGossip: allowIncomingGossip, @@ -60,18 +60,32 @@ func makeStreamManager(ctx context.Context, log logging.Logger, cfg nodeSubConfi // streamHandler is called by libp2p when a new stream is accepted func (n *streamManager) streamHandler(stream network.Stream) { + dispatched := false + remotePeer := stream.Conn().RemotePeer() + + defer func() { + if !dispatched { + n.host.ConnManager().Unprotect(remotePeer, cnmgrTag) + } + }() + if stream.Conn().Stat().Direction == network.DirInbound && !n.allowIncomingGossip { - n.log.Debugf("rejecting stream from incoming connection from %s", stream.Conn().RemotePeer().String()) + n.log.Debugf("rejecting stream from incoming connection from %s", remotePeer.String()) stream.Close() return } + // reject streams on connections not explicitly dialed by us + if stream.Conn().Stat().Direction == network.DirOutbound && stream.Stat().Direction == network.DirInbound { + if !n.host.ConnManager().IsProtected(remotePeer, cnmgrTag) { + n.log.Debugf("%s: ignoring incoming stream from non-dialed outgoing peer ID %s", stream.Conn().LocalPeer().String(), remotePeer.String()) + stream.Close() + return + } + } n.streamsLock.Lock() defer n.streamsLock.Unlock() - // could use stream.ID() for tracking; unique across all conns and peers - remotePeer := stream.Conn().RemotePeer() - if oldStream, ok := n.streams[remotePeer]; ok { // there's already a stream, for some reason, check if it's still open buf := []byte{} // empty buffer for checking @@ -84,34 +98,42 @@ func (n *streamManager) streamHandler(stream network.Stream) { // an error occurred while checking the old stream n.log.Infof("Failed to check old stream with %s: %v", remotePeer, err) } - n.streams[stream.Conn().RemotePeer()] = stream + // old stream is dead, remove + delete(n.streams, remotePeer) incoming := stream.Conn().Stat().Direction == network.DirInbound if err1 := n.dispatch(n.ctx, remotePeer, stream, incoming); err1 != nil { n.log.Errorln(err1.Error()) _ = stream.Reset() + return } + n.streams[stream.Conn().RemotePeer()] = stream + dispatched = true return } // otherwise, the old stream is still open, so we can close the new one stream.Close() + dispatched = true return } // no old stream - n.streams[stream.Conn().RemotePeer()] = stream incoming := stream.Conn().Stat().Direction == network.DirInbound if err := n.dispatch(n.ctx, remotePeer, stream, incoming); err != nil { n.log.Errorln(err.Error()) _ = stream.Reset() + return } + + n.streams[stream.Conn().RemotePeer()] = stream + + dispatched = true } // dispatch the stream to the appropriate handler func (n *streamManager) dispatch(ctx context.Context, remotePeer peer.ID, stream network.Stream, incoming bool) error { for _, pair := range n.handlers { if pair.ProtoID == stream.Protocol() { - pair.Handler(ctx, remotePeer, stream, incoming) - return nil + return pair.Handler(ctx, remotePeer, stream, incoming) } } n.log.Errorf("No handler for protocol %s, peer %s", stream.Protocol(), remotePeer) @@ -120,17 +142,22 @@ func (n *streamManager) dispatch(ctx context.Context, remotePeer peer.ID, stream // Connected is called when a connection is opened // for both incoming (listener -> addConn) and outgoing (dialer -> addConn) connections. +// This is invoked from libp2p's Swarm.notifyAll which holds a read lock on the notifiees list. +// We do some read/write operations in this handler for metadata exchange that creates a race condition +// with StopNotify on network shutdown. To avoid, run the handler as a goroutine. func (n *streamManager) Connected(net network.Network, conn network.Conn) { - remotePeer := conn.RemotePeer() localPeer := n.host.ID() if conn.Stat().Direction == network.DirInbound && !n.allowIncomingGossip { n.log.Debugf("%s: ignoring incoming connection from %s", localPeer.String(), remotePeer.String()) + n.host.ConnManager().Unprotect(conn.RemotePeer(), cnmgrTag) return } - // ensure that only one of the peers initiates the stream + // ensure that only one of the peers initiates the stream. + // the remote peer will open the stream and our streamHandler will handle it, + // so mark dispatched to preserve the cnmgr protection set by dialNode. if localPeer > remotePeer { n.log.Debugf("%s: ignoring a lesser peer ID %s", localPeer.String(), remotePeer.String()) return @@ -138,23 +165,32 @@ func (n *streamManager) Connected(net network.Network, conn network.Conn) { // check if this is outgoing connection but made not by us (serviceImpl.dialNode) // then it was made by some sub component like pubsub, ignore - if n.cfg.IsHybridServer() && conn.Stat().Direction == network.DirOutbound { - val, err := n.host.Peerstore().Get(remotePeer, psmdkDialed) - if err != nil || val != nil && !val.(bool) { - // not found or false value + if conn.Stat().Direction == network.DirOutbound { + if !n.host.ConnManager().IsProtected(remotePeer, cnmgrTag) { n.log.Debugf("%s: ignoring non-dialed outgoing peer ID %s", localPeer.String(), remotePeer.String()) return } - if val == nil { - n.log.Warnf("%s: failed to get dialed status for %s", localPeer.String(), remotePeer.String()) - } } + go n.handleConnected(conn) +} + +func (n *streamManager) handleConnected(conn network.Conn) { + dispatched := false + defer func() { + if !dispatched { + n.host.ConnManager().Unprotect(conn.RemotePeer(), cnmgrTag) + } + }() + remotePeer := conn.RemotePeer() + localPeer := n.host.ID() + n.streamsLock.Lock() _, ok := n.streams[remotePeer] + n.streamsLock.Unlock() if ok { - n.streamsLock.Unlock() n.log.Debugf("%s: already have a stream to/from %s", localPeer.String(), remotePeer.String()) + dispatched = true return // there's already an active stream with this peer for our protocol } @@ -165,20 +201,32 @@ func (n *streamManager) Connected(net network.Network, conn network.Conn) { stream, err := n.host.NewStream(n.ctx, remotePeer, protos...) if err != nil { n.log.Infof("%s: failed to open stream to %s (%s): %v", localPeer.String(), remotePeer, conn.RemoteMultiaddr().String(), err) - n.streamsLock.Unlock() return } - n.streams[remotePeer] = stream - n.streamsLock.Unlock() - n.log.Infof("%s: using protocol %s with peer %s", localPeer.String(), stream.Protocol(), remotePeer.String()) incoming := stream.Conn().Stat().Direction == network.DirInbound - err = n.dispatch(n.ctx, remotePeer, stream, incoming) - if err != nil { + if err = n.dispatch(n.ctx, remotePeer, stream, incoming); err != nil { n.log.Errorln(err.Error()) _ = stream.Reset() + return } + + n.streamsLock.Lock() + defer n.streamsLock.Unlock() + if _, exists := n.streams[remotePeer]; exists { + // another stream was added in the meantime, close this one and keep the existing one + _ = stream.Reset() + dispatched = true + return + } + // don't add disconnected / died conns, so Disconnect won't need to clean up + if stream.Conn().IsClosed() { + _ = stream.Reset() + return // dispatched is still false + } + n.streams[remotePeer] = stream + dispatched = true } // Disconnected is called when a connection is closed diff --git a/network/p2p/streams_stale_test.go b/network/p2p/streams_stale_test.go new file mode 100644 index 0000000000..84709e8c7c --- /dev/null +++ b/network/p2p/streams_stale_test.go @@ -0,0 +1,430 @@ +// Copyright (C) 2019-2026 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" + "errors" + "testing" + "time" + + connmgrcore "github.com/libp2p/go-libp2p/core/connmgr" + ic "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/event" + "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" + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/test/partitiontest" +) + +// errDispatchFailed is the sentinel error returned by the failing test handler. +var errDispatchFailed = errors.New("dispatch failed") + +const testProto = protocol.ID("/algorand-test/1.0.0") + +// mockConnMgr implements connmgrcore.ConnManager for testing. +type mockConnMgr struct { + mu deadlock.Mutex + protected map[peer.ID]map[string]bool +} + +func newMockConnMgr() *mockConnMgr { + return &mockConnMgr{protected: make(map[peer.ID]map[string]bool)} +} + +func (m *mockConnMgr) Protect(id peer.ID, tag string) { + m.mu.Lock() + defer m.mu.Unlock() + if m.protected[id] == nil { + m.protected[id] = make(map[string]bool) + } + m.protected[id][tag] = true +} + +func (m *mockConnMgr) Unprotect(id peer.ID, tag string) bool { + m.mu.Lock() + defer m.mu.Unlock() + if m.protected[id] != nil { + delete(m.protected[id], tag) + } + return len(m.protected[id]) > 0 +} + +func (m *mockConnMgr) IsProtected(id peer.ID, tag string) bool { + m.mu.Lock() + defer m.mu.Unlock() + return m.protected[id] != nil && m.protected[id][tag] +} + +func (m *mockConnMgr) TagPeer(peer.ID, string, int) {} +func (m *mockConnMgr) UntagPeer(peer.ID, string) {} +func (m *mockConnMgr) UpsertTag(peer.ID, string, func(int) int) {} +func (m *mockConnMgr) GetTagInfo(peer.ID) *connmgrcore.TagInfo { return nil } +func (m *mockConnMgr) TrimOpenConns(context.Context) {} +func (m *mockConnMgr) Notifee() network.Notifiee { return nil } +func (m *mockConnMgr) CheckLimit(connmgrcore.GetConnLimiter) error { return nil } +func (m *mockConnMgr) Close() error { return nil } + +// mockHost implements host.Host with only the methods used by streamManager. +type mockHost struct { + id peer.ID + cm *mockConnMgr + newStreamFn func(context.Context, peer.ID, ...protocol.ID) (network.Stream, error) +} + +func (h *mockHost) ID() peer.ID { return h.id } +func (h *mockHost) ConnManager() connmgrcore.ConnManager { return h.cm } +func (h *mockHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (network.Stream, error) { + return h.newStreamFn(ctx, p, pids...) +} + +func (h *mockHost) Peerstore() peerstore.Peerstore { panic("unused") } +func (h *mockHost) Addrs() []ma.Multiaddr { panic("unused") } +func (h *mockHost) Network() network.Network { panic("unused") } +func (h *mockHost) Mux() protocol.Switch { panic("unused") } +func (h *mockHost) Connect(context.Context, peer.AddrInfo) error { panic("unused") } +func (h *mockHost) SetStreamHandler(protocol.ID, network.StreamHandler) {} +func (h *mockHost) SetStreamHandlerMatch(protocol.ID, func(protocol.ID) bool, network.StreamHandler) { +} +func (h *mockHost) RemoveStreamHandler(protocol.ID) {} +func (h *mockHost) Close() error { return nil } +func (h *mockHost) EventBus() event.Bus { panic("unused") } + +// Verify interface satisfaction at compile time. +var _ host.Host = (*mockHost)(nil) + +// mockConn implements network.Conn with controllable direction and peer IDs. +type mockConn struct { + remotePeerID peer.ID + localPeerID peer.ID + dir network.Direction +} + +func newMockConn(local, remote peer.ID, dir network.Direction) *mockConn { + return &mockConn{localPeerID: local, remotePeerID: remote, dir: dir} +} + +func (c *mockConn) Close() error { return nil } +func (c *mockConn) LocalPeer() peer.ID { return c.localPeerID } +func (c *mockConn) RemotePeer() peer.ID { return c.remotePeerID } +func (c *mockConn) RemotePublicKey() ic.PubKey { return nil } +func (c *mockConn) ConnState() network.ConnectionState { return network.ConnectionState{} } +func (c *mockConn) LocalMultiaddr() ma.Multiaddr { return ma.StringCast("/ip4/127.0.0.1/tcp/4190") } +func (c *mockConn) RemoteMultiaddr() ma.Multiaddr { return ma.StringCast("/ip4/1.2.3.4/tcp/4190") } +func (c *mockConn) Stat() network.ConnStats { + return network.ConnStats{Stats: network.Stats{Direction: c.dir}} +} +func (c *mockConn) Scope() network.ConnScope { return nil } +func (c *mockConn) ID() string { return "mock-conn" } +func (c *mockConn) NewStream(context.Context) (network.Stream, error) { panic("unused") } +func (c *mockConn) GetStreams() []network.Stream { return nil } +func (c *mockConn) IsClosed() bool { return false } + +var _ network.Conn = (*mockConn)(nil) + +// mockStream implements network.Stream with controllable behavior. +type mockStream struct { + mu deadlock.Mutex + conn *mockConn + proto protocol.ID + dir network.Direction + readErr error // error returned by Read + resetCalled bool + closeCalled bool +} + +func newMockStream(conn *mockConn, proto protocol.ID, dir network.Direction) *mockStream { + return &mockStream{conn: conn, proto: proto, dir: dir} +} + +func (s *mockStream) Read(p []byte) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.readErr != nil { + return 0, s.readErr + } + return 0, nil +} + +func (s *mockStream) Write(p []byte) (int, error) { return len(p), nil } + +func (s *mockStream) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + s.closeCalled = true + return nil +} + +func (s *mockStream) CloseRead() error { return nil } +func (s *mockStream) CloseWrite() error { return nil } + +func (s *mockStream) Reset() error { + s.mu.Lock() + defer s.mu.Unlock() + s.resetCalled = true + return nil +} + +func (s *mockStream) SetDeadline(time.Time) error { return nil } +func (s *mockStream) SetReadDeadline(time.Time) error { return nil } +func (s *mockStream) SetWriteDeadline(time.Time) error { return nil } +func (s *mockStream) Protocol() protocol.ID { return s.proto } +func (s *mockStream) SetProtocol(protocol.ID) error { return nil } +func (s *mockStream) Stat() network.Stats { return network.Stats{Direction: s.dir} } +func (s *mockStream) Conn() network.Conn { return s.conn } +func (s *mockStream) ID() string { return "mock-stream" } +func (s *mockStream) Scope() network.StreamScope { return nil } + +func (s *mockStream) wasReset() bool { + s.mu.Lock() + defer s.mu.Unlock() + return s.resetCalled +} + +var _ network.Stream = (*mockStream)(nil) + +// failingHandler is a StreamHandler that always returns errDispatchFailed. +func failingHandler(_ context.Context, _ peer.ID, _ network.Stream, _ bool) error { + return errDispatchFailed +} + +// newTestStreamManager creates a streamManager with a failing handler for testProto. +func newTestStreamManager(localID peer.ID, allowIncoming bool) (*streamManager, *mockHost) { + cm := newMockConnMgr() + h := &mockHost{id: localID, cm: cm} + handlers := StreamHandlers{ + {ProtoID: testProto, Handler: failingHandler}, + } + logger := logging.NewLogger() + logger.SetLevel(logging.Debug) + sm := makeStreamManager(context.Background(), logger, h, handlers, allowIncoming) + return sm, h +} + +// assertStreamMapEmpty checks that sm.streams has no entry for remotePeer. +func assertStreamMapEmpty(t *testing.T, sm *streamManager, remotePeer peer.ID) { + t.Helper() + sm.streamsLock.Lock() + defer sm.streamsLock.Unlock() + _, exists := sm.streams[remotePeer] + require.False(t, exists, "expected n.streams[%s] to be cleaned up after dispatch failure", remotePeer) +} + +// --- test cases --- + +// TestStream_MapCleanupOnDispatchFailure verifies that n.streams is cleaned up +// when dispatch (V22 handshake) fails, across all 8 combinations: +// +// directions (inbound/outbound) × +// peer ID orderings (local < remote / local > remote) × +// dial origins (dialNode / DHT-pubsub) +func TestStream_MapCleanupOnDispatchFailure(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + // deterministic peer IDs + lowPeer := peer.ID("AAAA-low-peer") + highPeer := peer.ID("ZZZZ-high-peer") + require.True(t, lowPeer < highPeer) + + // handleConnected path — local node initiates the stream + + // case 1: outbound, localPeer < remotePeer, dialNode + // Connected, peer ID passes, protected, handleConnected, dispatch fails + t.Run("outbound_localLow_dialNode", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(lowPeer, true) + conn := newMockConn(lowPeer, highPeer, network.DirOutbound) + stream := newMockStream(conn, testProto, network.DirOutbound) + h.newStreamFn = func(context.Context, peer.ID, ...protocol.ID) (network.Stream, error) { + return stream, nil + } + // dialNode would have protected the peer before dialing + h.cm.Protect(highPeer, cnmgrTag) + + sm.handleConnected(conn) + + assertStreamMapEmpty(t, sm, highPeer) + require.True(t, stream.wasReset()) + require.False(t, h.cm.IsProtected(highPeer, cnmgrTag)) + }) + + // case 2: outbound, localPeer < remotePeer, DHT dial + // Connected skips (unprotected). DialPeersUntilTargetCount, Protect, handleConnected, dispatch fails + t.Run("outbound_localLow_dhtDial", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(lowPeer, true) + conn := newMockConn(lowPeer, highPeer, network.DirOutbound) + stream := newMockStream(conn, testProto, network.DirOutbound) + h.newStreamFn = func(context.Context, peer.ID, ...protocol.ID) (network.Stream, error) { + return stream, nil + } + // DialPeersUntilTargetCount protects then calls handleConnected + h.cm.Protect(highPeer, cnmgrTag) + + sm.handleConnected(conn) + + assertStreamMapEmpty(t, sm, highPeer) + require.True(t, stream.wasReset()) + require.False(t, h.cm.IsProtected(highPeer, cnmgrTag)) + }) + + // case 3: outbound, localPeer > remotePeer, DHT dial + // Connected defers (peer ID). Remote's stream rejected (unprotected outbound). + // DialPeersUntilTargetCount, Protect, handleConnected (new code skips peer ID check), dispatch fails + t.Run("outbound_localHigh_dhtDial", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(highPeer, true) + conn := newMockConn(highPeer, lowPeer, network.DirOutbound) + stream := newMockStream(conn, testProto, network.DirOutbound) + h.newStreamFn = func(context.Context, peer.ID, ...protocol.ID) (network.Stream, error) { + return stream, nil + } + h.cm.Protect(lowPeer, cnmgrTag) + + sm.handleConnected(conn) + + assertStreamMapEmpty(t, sm, lowPeer) + require.True(t, stream.wasReset()) + require.False(t, h.cm.IsProtected(lowPeer, cnmgrTag)) + }) + + // case 4: inbound, localPeer < remotePeer, remote's dialNode dialed + // Connected, inbound gossip OK, peer ID passes, handleConnected, dispatch fails + t.Run("inbound_localLow_remoteDial", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(lowPeer, true) + conn := newMockConn(lowPeer, highPeer, network.DirInbound) + stream := newMockStream(conn, testProto, network.DirInbound) + h.newStreamFn = func(context.Context, peer.ID, ...protocol.ID) (network.Stream, error) { + return stream, nil + } + + sm.handleConnected(conn) + + assertStreamMapEmpty(t, sm, highPeer) + require.True(t, stream.wasReset()) + }) + + // case 5: inbound, localPeer < remotePeer, remote's DHT dialed + t.Run("inbound_localLow_remoteDHT", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(lowPeer, true) + conn := newMockConn(lowPeer, highPeer, network.DirInbound) + stream := newMockStream(conn, testProto, network.DirInbound) + h.newStreamFn = func(context.Context, peer.ID, ...protocol.ID) (network.Stream, error) { + return stream, nil + } + // No protection from our side (remote's inbound conn) + + sm.handleConnected(conn) + + assertStreamMapEmpty(t, sm, highPeer) + require.True(t, stream.wasReset()) + // Unprotect was called but was a no-op (nothing was protected) + require.False(t, h.cm.IsProtected(highPeer, cnmgrTag)) + }) + + // streamHandler path — remote peer creates the stream, our node handles it + + // case 6: outbound, localPeer > remotePeer, our dialNode + // Connected defers (peer ID). Remote opens stream, our streamHandler, dispatch fails + t.Run("outbound_localHigh_ourDial", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(highPeer, true) + // Connection is outbound (we dialed), stream is inbound (remote initiated) + conn := newMockConn(highPeer, lowPeer, network.DirOutbound) + stream := newMockStream(conn, testProto, network.DirInbound) + // Our dialNode protected this peer + h.cm.Protect(lowPeer, cnmgrTag) + + sm.streamHandler(stream) + + assertStreamMapEmpty(t, sm, lowPeer) + require.True(t, stream.wasReset()) + // dispatched=false => Unprotect called + require.False(t, h.cm.IsProtected(lowPeer, cnmgrTag)) + }) + + // case 7: inbound, localPeer > remotePeer, remote's dialNode dialed us + // Connected defers (peer ID). Remote opens stream, our streamHandler, dispatch fails + t.Run("inbound_localHigh_remoteDial", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(highPeer, true) + // Connection is inbound (remote dialed us), stream is inbound (remote initiated) + conn := newMockConn(highPeer, lowPeer, network.DirInbound) + stream := newMockStream(conn, testProto, network.DirInbound) + + sm.streamHandler(stream) + + assertStreamMapEmpty(t, sm, lowPeer) + require.True(t, stream.wasReset()) + // No protection was set, so Unprotect is a no-op + require.False(t, h.cm.IsProtected(lowPeer, cnmgrTag)) + }) + + // case 8: inbound, localPeer > remotePeer, remote's DHT dialed us + // Connected defers (peer ID). Remote's DHT connection; + // remote opens stream, our streamHandler, dispatch fails + t.Run("inbound_localHigh_remoteDHT", func(t *testing.T) { + t.Parallel() + sm, h := newTestStreamManager(highPeer, true) + conn := newMockConn(highPeer, lowPeer, network.DirInbound) + stream := newMockStream(conn, testProto, network.DirInbound) + + sm.streamHandler(stream) + + assertStreamMapEmpty(t, sm, lowPeer) + require.True(t, stream.wasReset()) + require.False(t, h.cm.IsProtected(lowPeer, cnmgrTag)) + }) +} + +// TestStream_HandlerCleanupReplacingDeadStream verifies that when streamHandler +// replaces a dead stream and the new dispatch also fails, the map entry is cleaned up. +func TestStream_HandlerCleanupReplacingDeadStream(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + localID := peer.ID("ZZZZ-high-peer") + remoteID := peer.ID("AAAA-low-peer") + sm, h := newTestStreamManager(localID, true) + + // Pre-populate n.streams with a dead (reset) stream + conn := newMockConn(localID, remoteID, network.DirInbound) + deadStream := newMockStream(conn, testProto, network.DirInbound) + deadStream.readErr = network.ErrReset // Read returns error => stream is dead + sm.streams[remoteID] = deadStream + + // Protect so that Unprotect tracking works + h.cm.Protect(remoteID, cnmgrTag) + + // New stream arrives from remote peer, dispatch will fail + newStream := newMockStream(conn, testProto, network.DirInbound) + sm.streamHandler(newStream) + + assertStreamMapEmpty(t, sm, remoteID) + require.True(t, newStream.wasReset(), "new stream should be reset on dispatch failure") +} diff --git a/network/p2p/streams_test.go b/network/p2p/streams_test.go index 6e71755d4f..17474ed6b2 100644 --- a/network/p2p/streams_test.go +++ b/network/p2p/streams_test.go @@ -17,30 +17,57 @@ package p2p import ( + "bytes" "context" "strings" "testing" "time" - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/network/p2p/peerstore" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" + + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/test/partitiontest" ) +// syncBuffer is a thread-safe bytes.Buffer for use as a log output target. +type syncBuffer struct { + mu deadlock.Mutex + buf bytes.Buffer +} + +func (sb *syncBuffer) Write(p []byte) (int, error) { + sb.mu.Lock() + defer sb.mu.Unlock() + return sb.buf.Write(p) +} + +func (sb *syncBuffer) String() string { + sb.mu.Lock() + defer sb.mu.Unlock() + return sb.buf.String() +} + +func (sb *syncBuffer) Reset() { + sb.mu.Lock() + defer sb.mu.Unlock() + sb.buf.Reset() +} + // TestConnectedLogsNonDialedOutgoingConnection tests that the Connected function // exits early for non-dialed outgoing connections by checking the log output func TestStreamNonDialedOutgoingConnection(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - logBuffer := &strings.Builder{} + logBuffer := &syncBuffer{} logger := logging.NewLogger() logger.SetOutput(logBuffer) logger.SetLevel(logging.Debug) @@ -82,8 +109,8 @@ func TestStreamNonDialedOutgoingConnection(t *testing.T) { ctx := context.Background() handlers := StreamHandlers{} - dialerSM = makeStreamManager(ctx, logger, cfg, dialerHost, handlers, false) - listenerSM = makeStreamManager(ctx, logger, cfg, listenerHost, handlers, false) + dialerSM = makeStreamManager(ctx, logger, dialerHost, handlers, false) + listenerSM = makeStreamManager(ctx, logger, listenerHost, handlers, false) // Setup Connected notification dialerHost.Network().Notify(dialerSM) @@ -110,9 +137,9 @@ func TestStreamNonDialedOutgoingConnection(t *testing.T) { require.Len(t, conns, 1) require.Equal(t, network.DirOutbound, conns[0].Stat().Direction) - // Check that the log contains the expected message for non-dialed outgoing connection - logOutput := logBuffer.String() - expectedMsg := "ignoring non-dialed outgoing peer ID" - require.Contains(t, logOutput, expectedMsg) - require.Contains(t, logOutput, listenerHost.ID().String()) + const expectedMsg = "ignoring non-dialed outgoing peer ID" + require.Eventually(t, func() bool { + logOutput := logBuffer.String() + return strings.Contains(logOutput, expectedMsg) && strings.Contains(logOutput, listenerHost.ID().String()) + }, 5*time.Second, 50*time.Millisecond) } diff --git a/network/p2p/testing/httpNode.go b/network/p2p/testing/httpNode.go index b1e8555ce5..8a538173e4 100644 --- a/network/p2p/testing/httpNode.go +++ b/network/p2p/testing/httpNode.go @@ -23,13 +23,14 @@ 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" + + "github.com/algorand/go-algorand/components/mocks" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/network/p2p" ) // HTTPNode is a mock network node that uses libp2p and http. @@ -66,7 +67,9 @@ func (p *HTTPNode) RegisterHandlers(dispatch []network.TaggedMessageHandler) {} func (p *HTTPNode) Start() error { go func() { err := p.httpServer.Serve() - require.NoError(p.tb, err) + if err != nil { + require.ErrorIs(p.tb, err, http.ErrServerClosed) + } }() return nil } diff --git a/network/p2pMetainfo.go b/network/p2pMetainfo.go index 0646e817f6..10f11faa8d 100644 --- a/network/p2pMetainfo.go +++ b/network/p2pMetainfo.go @@ -23,8 +23,9 @@ import ( "math" "net/http" - "github.com/algorand/go-algorand/data/basics" "github.com/libp2p/go-libp2p/core/peer" + + "github.com/algorand/go-algorand/data/basics" ) // peerMetaHeaders holds peer metadata headers similar to wsnet http.Header @@ -69,17 +70,17 @@ type peerMetaInfo struct { func readPeerMetaHeaders(stream io.ReadWriter, p2pPeer peer.ID, netProtoSupportedVersions []string) (peerMetaInfo, error) { var msgLenBytes [2]byte - rn, err := stream.Read(msgLenBytes[:]) - if rn != 2 || err != nil { + _, err := io.ReadFull(stream, msgLenBytes[:]) + if err != nil { err0 := fmt.Errorf("error reading response message length from peer %s: %w", p2pPeer, err) return peerMetaInfo{}, err0 } msgLen := binary.BigEndian.Uint16(msgLenBytes[:]) msgBytes := make([]byte, msgLen) - rn, err = stream.Read(msgBytes[:]) - if rn != int(msgLen) || err != nil { - err0 := fmt.Errorf("error reading response message from peer %s: %w, expected: %d, read: %d", p2pPeer, err, msgLen, rn) + _, err = io.ReadFull(stream, msgBytes) + if err != nil { + err0 := fmt.Errorf("error reading response message from peer %s: %w", p2pPeer, err) return peerMetaInfo{}, err0 } var responseHeaders peerMetaHeaders diff --git a/network/p2pMetainfo_test.go b/network/p2pMetainfo_test.go index 2d65ee2362..50aaf0f89a 100644 --- a/network/p2pMetainfo_test.go +++ b/network/p2pMetainfo_test.go @@ -22,11 +22,12 @@ import ( "net/http" "testing" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/test/partitiontest" ) // MockStream is a io.ReaderWriter testing mock @@ -80,29 +81,28 @@ func TestReadPeerMetaHeaders(t *testing.T) { assert.Equal(t, "mockFeatures", metaInfo.features) mockStream.AssertExpectations(t) - // Error case: incomplete length read + // Error case: incomplete length read then EOF mockStream = new(MockStream) mockStream.On("Read", mock.Anything).Return([]byte{1}, nil).Once() + mockStream.On("Read", mock.Anything).Return([]byte{}, fmt.Errorf("EOF")).Once() _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) - assert.Error(t, err) - assert.Contains(t, err.Error(), "error reading response message length") + assert.ErrorContains(t, err, "error reading response message length") mockStream.AssertExpectations(t) // Error case: error reading length mockStream = new(MockStream) mockStream.On("Read", mock.Anything).Return([]byte{}, fmt.Errorf("read error")).Once() _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) - assert.Error(t, err) - assert.Contains(t, err.Error(), "error reading response message length") + assert.ErrorContains(t, err, "error reading response message length") mockStream.AssertExpectations(t) - // Error case: incomplete message read + // Error case: incomplete message read then EOF mockStream = new(MockStream) mockStream.On("Read", mock.Anything).Return(lengthBytes, nil).Once() - mockStream.On("Read", mock.Anything).Return(data[:len(data)/2], nil).Once() // Return only half the data + mockStream.On("Read", mock.Anything).Return(data[:len(data)/2], nil).Once() + mockStream.On("Read", mock.Anything).Return([]byte{}, fmt.Errorf("EOF")).Once() _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) - assert.Error(t, err) - assert.Contains(t, err.Error(), "error reading response message") + assert.ErrorContains(t, err, "error reading response message") mockStream.AssertExpectations(t) // Error case: error reading message @@ -110,8 +110,7 @@ func TestReadPeerMetaHeaders(t *testing.T) { mockStream.On("Read", mock.Anything).Return(lengthBytes, nil).Once() mockStream.On("Read", mock.Anything).Return([]byte{}, fmt.Errorf("read error")).Once() _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) - assert.Error(t, err) - assert.Contains(t, err.Error(), "error reading response message") + assert.ErrorContains(t, err, "error reading response message") mockStream.AssertExpectations(t) // Error case: invalid messagepack (unmarshaling error) @@ -121,8 +120,7 @@ func TestReadPeerMetaHeaders(t *testing.T) { mockStream.On("Read", mock.Anything).Return(corruptedMsgpLength, nil).Once() mockStream.On("Read", mock.Anything).Return([]byte{0x99, 0x01, 0x02}, nil).Once() _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) - assert.Error(t, err) - assert.Contains(t, err.Error(), "error unmarshaling response message") + assert.ErrorContains(t, err, "error unmarshaling response message") mockStream.AssertExpectations(t) // Error case: no matching protocol version @@ -138,8 +136,27 @@ func TestReadPeerMetaHeaders(t *testing.T) { mockStream.On("Read", mock.Anything).Return(incompatibleLengthBytes, nil).Once() mockStream.On("Read", mock.Anything).Return(incompatibleData, nil).Once() _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) - assert.Error(t, err) - assert.Contains(t, err.Error(), "does not support any of the supported protocol versions") + assert.ErrorContains(t, err, "does not support any of the supported protocol versions") + mockStream.AssertExpectations(t) + + // Verify short reads are handled: length arrives in two reads + mockStream = new(MockStream) + mockStream.On("Read", mock.Anything).Return(lengthBytes[:1], nil).Once() + mockStream.On("Read", mock.Anything).Return(lengthBytes[1:], nil).Once() + mockStream.On("Read", mock.Anything).Return(data, nil).Once() + metaInfo, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) + assert.NoError(t, err) + assert.Equal(t, "1.0", metaInfo.version) + mockStream.AssertExpectations(t) + + // Verify short reads are handled: body arrives in two reads + mockStream = new(MockStream) + mockStream.On("Read", mock.Anything).Return(lengthBytes, nil).Once() + mockStream.On("Read", mock.Anything).Return(data[:len(data)/2], nil).Once() + mockStream.On("Read", mock.Anything).Return(data[len(data)/2:], nil).Once() + metaInfo, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions) + assert.NoError(t, err) + assert.Equal(t, "1.0", metaInfo.version) mockStream.AssertExpectations(t) } @@ -171,7 +188,6 @@ func TestWritePeerMetaHeaders(t *testing.T) { mockStream = new(MockStream) mockStream.On("Write", mock.Anything).Return(0, fmt.Errorf("write error")).Once() err = writePeerMetaHeaders(mockStream, p2pPeer, "1.0", n) - assert.Error(t, err) - assert.Contains(t, err.Error(), "error sending initial message") + assert.ErrorContains(t, err, "error sending initial message") mockStream.AssertExpectations(t) } diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index 452b843369..7f7b0f646f 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -25,6 +25,14 @@ import ( "sync/atomic" "time" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" algocrypto "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" @@ -35,13 +43,6 @@ import ( "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/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" ) // some arbitrary number TODO: figure out a better value based on peerSelector/fetcher algorithm @@ -592,9 +593,9 @@ func (n *P2PNetwork) httpdThread() { defer n.wg.Done() err := n.httpServer.Serve() - if err != nil { + if err == http.ErrServerClosed { + } else if err != nil { n.log.Errorf("Error serving libp2phttp: %v", err) - return } } @@ -901,34 +902,37 @@ func (n *P2PNetwork) VoteCompressionEnabled() bool { // wsStreamHandlerV1 is a callback that the p2p package calls when a new peer connects and establishes a // stream for the websocket protocol. // TODO: remove after consensus v41 takes effect. -func (n *P2PNetwork) wsStreamHandlerV1(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) { +func (n *P2PNetwork) wsStreamHandlerV1(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) error { if stream.Protocol() != p2p.AlgorandWsProtocolV1 { - n.log.Warnf("unknown protocol %s from peer %s", stream.Protocol(), p2pPeer) - return + err := fmt.Errorf("unknown protocol %s from peer %s", stream.Protocol(), p2pPeer) + n.log.Warn(err.Error()) + return err } if incoming { var initMsg [1]byte rn, err := stream.Read(initMsg[:]) if rn == 0 || err != nil { - n.log.Warnf("wsStreamHandlerV1: error reading initial message from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) - return + err1 := fmt.Errorf("wsStreamHandlerV1: error reading initial message from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + n.log.Warn(err1.Error()) + return err1 } } else { - _, err := stream.Write([]byte("1")) - if err != nil { - n.log.Warnf("wsStreamHandlerV1: error sending initial message: %v", err) - return + if _, err := stream.Write([]byte("1")); err != nil { + err1 := fmt.Errorf("wsStreamHandlerV1: error sending initial message to peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + n.log.Warn(err1.Error()) + return err1 } } - n.baseWsStreamHandler(ctx, p2pPeer, stream, incoming, peerMetaInfo{}) + return n.baseWsStreamHandler(ctx, p2pPeer, stream, incoming, peerMetaInfo{}) } -func (n *P2PNetwork) wsStreamHandlerV22(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) { +func (n *P2PNetwork) wsStreamHandlerV22(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) error { if stream.Protocol() != p2p.AlgorandWsProtocolV22 { - n.log.Warnf("unknown protocol %s from peer%s", stream.Protocol(), p2pPeer) - return + err := fmt.Errorf("unknown protocol %s from peer %s", stream.Protocol(), p2pPeer) + n.log.Warn(err.Error()) + return err } var err error @@ -936,35 +940,39 @@ func (n *P2PNetwork) wsStreamHandlerV22(ctx context.Context, p2pPeer peer.ID, st if incoming { pmi, err = readPeerMetaHeaders(stream, p2pPeer, n.supportedProtocolVersions) if err != nil { - n.log.Warnf("wsStreamHandlerV22: error reading peer meta headers response from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + err1 := fmt.Errorf("wsStreamHandlerV22: error reading peer meta headers response from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + n.log.Warn(err1.Error()) _ = stream.Reset() - return + return err1 } err = writePeerMetaHeaders(stream, p2pPeer, pmi.version, n) if err != nil { - n.log.Warnf("wsStreamHandlerV22: error writing peer meta headers response to peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + err1 := fmt.Errorf("wsStreamHandlerV22: error writing peer meta headers response to peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + n.log.Warn(err1.Error()) _ = stream.Reset() - return + return err1 } } else { err = writePeerMetaHeaders(stream, p2pPeer, n.protocolVersion, n) if err != nil { - n.log.Warnf("wsStreamHandlerV22: error writing peer meta headers response to peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + err1 := fmt.Errorf("wsStreamHandlerV22: error writing peer meta headers response to peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + n.log.Warn(err1.Error()) _ = stream.Reset() - return + return err1 } // read the response pmi, err = readPeerMetaHeaders(stream, p2pPeer, n.supportedProtocolVersions) if err != nil { - n.log.Warnf("wsStreamHandlerV22: error reading peer meta headers response from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + err1 := fmt.Errorf("wsStreamHandlerV22: error reading peer meta headers response from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err) + n.log.Warn(err1.Error()) _ = stream.Reset() - return + return err1 } } - n.baseWsStreamHandler(ctx, p2pPeer, stream, incoming, pmi) + return n.baseWsStreamHandler(ctx, p2pPeer, stream, incoming, pmi) } -func (n *P2PNetwork) baseWsStreamHandler(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool, pmi peerMetaInfo) { +func (n *P2PNetwork) baseWsStreamHandler(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool, pmi peerMetaInfo) error { // get address for peer ID ma := stream.Conn().RemoteMultiaddr() addr := ma.String() @@ -1023,18 +1031,20 @@ func (n *P2PNetwork) baseWsStreamHandler(ctx context.Context, p2pPeer peer.ID, s 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") + err := fmt.Errorf("peer deduplicated before adding because the identity is already known: remote %s, local %s", addr, localAddr) + n.log.Warn(err.Error()) stream.Close() - return + return err } wsp.init(n.config, outgoingMessagesBufferSize) n.wsPeersLock.Lock() if wsp.didSignalClose.Load() == 1 { networkPeerAlreadyClosed.Inc(nil) - n.log.Debugf("peer closing %s", addr) + err := fmt.Errorf("peer closing %s", addr) + n.log.Debug(err.Error()) n.wsPeersLock.Unlock() - return + return err } n.wsPeers[p2pPeer] = wsp n.wsPeersToIDs[wsp] = p2pPeer @@ -1062,6 +1072,7 @@ func (n *P2PNetwork) baseWsStreamHandler(ctx context.Context, p2pPeer peer.ID, s Incoming: incoming, InstanceName: wsp.InstanceName, }) + return nil } // peerRemoteClose called from wsPeer to report that it has closed @@ -1071,6 +1082,7 @@ func (n *P2PNetwork) peerRemoteClose(peer *wsPeer, reason disconnectReason) { } func (n *P2PNetwork) removePeer(peer *wsPeer, remotePeerID peer.ID, reason disconnectReason) { + n.service.UnprotectPeer(remotePeerID) n.wsPeersLock.Lock() n.identityTracker.removeIdentity(peer) delete(n.wsPeers, remotePeerID) diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 2e5f1617a0..290e274940 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -28,6 +28,17 @@ import ( "testing" "time" + pubsub "github.com/libp2p/go-libp2p-pubsub" + pb "github.com/libp2p/go-libp2p-pubsub/pb" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" + + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" algocrypto "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/logging" @@ -39,16 +50,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/uuid" - "github.com/algorand/go-deadlock" - - pubsub "github.com/libp2p/go-libp2p-pubsub" - pb "github.com/libp2p/go-libp2p-pubsub/pb" - "github.com/libp2p/go-libp2p/core/crypto" - "github.com/libp2p/go-libp2p/core/network" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multiaddr" - ma "github.com/multiformats/go-multiaddr" - "github.com/stretchr/testify/require" ) func (n *P2PNetwork) hasPeers() bool { @@ -362,6 +363,9 @@ func (s *mockService) ClosePeer(peer peer.ID) error { return nil } +func (s *mockService) UnprotectPeer(peer.ID) { +} + func (s *mockService) Conns() []network.Conn { return nil } @@ -853,6 +857,19 @@ func TestP2PHTTPHandlerAllInterfaces(t *testing.T) { } +// baseTestMeshCreator is a simple wrapper to baseMeshCreator for TestP2PRelay +// in order to provide custom GossipSubHeartbeatInterval option via makeConfig +// to speed up heartbeat to reduce test flakiness of TestP2PRelay from pubsub mesh establishment timing +type baseTestMeshCreator struct { + baseMeshCreator +} + +func (c baseTestMeshCreator) makeConfig(wsnet *WebsocketNetwork, p2pnet *P2PNetwork) networkConfig { + return networkConfig{ + pubsubOpts: []p2p.PubSubOption{p2p.SetPubSubHeartbeatInterval(200 * time.Millisecond)}, + } +} + // 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. @@ -862,11 +879,6 @@ func TestP2PHTTPHandlerAllInterfaces(t *testing.T) { func TestP2PRelay(t *testing.T) { partitiontest.PartitionTest(t) - // Speed up heartbeat to reduce test flakiness from mesh establishment timing - oldHeartbeatInterval := pubsub.GossipSubHeartbeatInterval - pubsub.GossipSubHeartbeatInterval = 200 * time.Millisecond - defer func() { pubsub.GossipSubHeartbeatInterval = oldHeartbeatInterval }() - cfg := config.GetDefaultLocal() cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses cfg.ForceFetchTransactions = true @@ -875,7 +887,7 @@ func TestP2PRelay(t *testing.T) { log := logging.TestingLog(t) genesisInfo := GenesisInfo{genesisID, config.Devtestnet} log.Debugln("Starting netA") - netA, err := NewP2PNetwork(log.With("net", "netA"), cfg, "", nil, genesisInfo, &nopeNodeInfo{}, nil, nil) + netA, err := NewP2PNetwork(log.With("net", "netA"), cfg, "", nil, genesisInfo, &nopeNodeInfo{}, nil, baseTestMeshCreator{}) require.NoError(t, err) err = netA.Start() diff --git a/network/p2pPeer.go b/network/p2pPeer.go index fae62f5379..9eff715488 100644 --- a/network/p2pPeer.go +++ b/network/p2pPeer.go @@ -23,12 +23,13 @@ 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" + + "github.com/algorand/websocket" + + "github.com/algorand/go-algorand/logging" ) type wsPeerConnP2P struct { diff --git a/network/phonebook/phonebook_test.go b/network/phonebook/phonebook_test.go index ed2da2dbaf..a2f3a5ba95 100644 --- a/network/phonebook/phonebook_test.go +++ b/network/phonebook/phonebook_test.go @@ -21,8 +21,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func testPhonebookAll(t *testing.T, set []string, ph Phonebook) { diff --git a/network/topics_test.go b/network/topics_test.go index 0c830bc0a5..35a50f6c0b 100644 --- a/network/topics_test.go +++ b/network/topics_test.go @@ -21,8 +21,9 @@ import ( "fmt" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) // Test the marshall/unmarshall of Topics diff --git a/network/vpack/dynamic_vpack_test.go b/network/vpack/dynamic_vpack_test.go index 612c9dc404..7820b25526 100644 --- a/network/vpack/dynamic_vpack_test.go +++ b/network/vpack/dynamic_vpack_test.go @@ -23,11 +23,12 @@ import ( "testing" "unsafe" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // TestStatefulEncoderDecoderSequence verifies that a StatefulEncoder/StatefulDecoder diff --git a/network/vpack/lru_table_test.go b/network/vpack/lru_table_test.go index 3e5d0a9c53..4bc96ec9c6 100644 --- a/network/vpack/lru_table_test.go +++ b/network/vpack/lru_table_test.go @@ -22,9 +22,10 @@ import ( "testing" "testing/quick" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestLRUTableSizeValidation(t *testing.T) { @@ -32,13 +33,11 @@ func TestLRUTableSizeValidation(t *testing.T) { // Test invalid size (not power of 2) _, err := NewStatefulEncoder(100) - require.Error(t, err) - require.Contains(t, err.Error(), "must be a power of 2") + require.ErrorContains(t, err, "must be a power of 2") // Test invalid size (too small) _, err = NewStatefulEncoder(8) - require.Error(t, err) - require.Contains(t, err.Error(), "at least 16") + require.ErrorContains(t, err, "at least 16") // Test valid sizes for _, size := range []uint{16, 32, 64, 128, 256, 512, 1024, 2048} { diff --git a/network/vpack/parse_test.go b/network/vpack/parse_test.go index f69c577953..def4d5858c 100644 --- a/network/vpack/parse_test.go +++ b/network/vpack/parse_test.go @@ -19,10 +19,11 @@ package vpack import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" ) // a string that is greater than the max 5-bit fixmap size @@ -187,3 +188,66 @@ func TestParseVoteTrailingDataErr(t *testing.T) { _, err := se.CompressVote(nil, buf) assert.ErrorContains(t, err, "unexpected trailing data") } + +// TestUncompressedMsgpackDetection tests that decompression errors on uncompressed +// msgpack data get wrapped with ErrLikelyUncompressed, while corrupted vpack data does not. +func TestUncompressedMsgpackDetection(t *testing.T) { + partitiontest.PartitionTest(t) + + // Create a vote structure shared by both subtests + vote := map[string]any{ + "cred": map[string]any{"pf": crypto.VrfProof{7, 8, 9}}, + "r": map[string]any{ + "rnd": uint64(1000), + "per": uint64(5), + "step": uint64(2), + "snd": [32]byte{1, 2, 3}, + "prop": map[string]any{ + "dig": [32]byte{4, 5, 6}, + }, + }, + "sig": map[string]any{ + "s": [64]byte{10, 11, 12}, + "p": [32]byte{13, 14, 15}, + "p2": [32]byte{16, 17, 18}, + "p1s": [64]byte{22, 23, 24}, + "p2s": [64]byte{25, 26, 27}, + "ps": [64]byte{}, + }, + } + + t.Run("uncompressed_detected", func(t *testing.T) { + msgpackData := protocol.EncodeReflect(vote) + + // Verify it starts with the uncompressed msgpack pattern + assert.GreaterOrEqual(t, len(msgpackData), 6) + assert.Equal(t, byte(0x83), msgpackData[0], "uncompressed vote should start with fixmap(3)") + assert.Equal(t, byte(0xa4), msgpackData[1], "followed by fixstr(4)") + assert.Equal(t, "cred", string(msgpackData[2:6]), "field name should be 'cred'") + + // Try to decompress as vpack - should fail with ErrLikelyUncompressed + var dec StatelessDecoder + _, err := dec.DecompressVote(nil, msgpackData) + + assert.Error(t, err) + assert.ErrorIs(t, err, ErrLikelyUncompressed, "should detect uncompressed msgpack pattern") + assert.ErrorContains(t, err, "data appears to be uncompressed msgpack") + }) + + t.Run("corrupted_vpack_not_detected", func(t *testing.T) { + msgpackData := protocol.EncodeReflect(vote) + var enc StatelessEncoder + compressed, err := enc.CompressVote(nil, msgpackData) + assert.NoError(t, err) + + // Corrupt the compressed data by truncating it + corrupted := compressed[:len(compressed)/2] + + // Try to decompress - should fail but NOT with ErrLikelyUncompressed + var dec StatelessDecoder + _, err = dec.DecompressVote(nil, corrupted) + + assert.Error(t, err) + assert.NotErrorIs(t, err, ErrLikelyUncompressed, "corrupted vpack should not be detected as uncompressed msgpack") + }) +} diff --git a/network/vpack/proposal_window_test.go b/network/vpack/proposal_window_test.go index 0161107240..a8738ba8d3 100644 --- a/network/vpack/proposal_window_test.go +++ b/network/vpack/proposal_window_test.go @@ -19,8 +19,9 @@ package vpack import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func makeTestPropBundle(seed byte) proposalEntry { diff --git a/network/vpack/rapid_test.go b/network/vpack/rapid_test.go index fc58c57528..32c63a0bd3 100644 --- a/network/vpack/rapid_test.go +++ b/network/vpack/rapid_test.go @@ -23,12 +23,13 @@ import ( "testing" "unsafe" + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" - "pgregory.net/rapid" ) // TestCheckStatelessEncoder tests the StatelessEncoder/Decoder using randomly generated votes diff --git a/network/vpack/vpack.go b/network/vpack/vpack.go index 4d20275b97..5bafe269df 100644 --- a/network/vpack/vpack.go +++ b/network/vpack/vpack.go @@ -215,9 +215,31 @@ func (d *StatelessDecoder) proposalValueMapSize(mask uint8) uint8 { return uint8(bits.OnesCount8(mask & (bitDig | bitEncDig | bitOper | bitOprop))) } +// ErrLikelyUncompressed is returned when vpack decompression detects what appears +// to be uncompressed msgpack data from a peer claiming vpack support. +var ErrLikelyUncompressed = fmt.Errorf("data appears to be uncompressed msgpack") + +// isLikelyUncompressedMsgpack checks if the source data looks like an uncompressed +// msgpack vote that was mistakenly treated as vpack-compressed. +func isLikelyUncompressedMsgpack(src []byte) bool { + // uncompressed msgpack votes start with 0x83 (fixmap marker with 3 elements: cred, r, sig), + // followed by 0xa4 (fixstr marker of length 4), then "cred" + return len(src) > 5 && src[0] == 0x83 && src[1] == 0xa4 && + src[2] == 'c' && src[3] == 'r' && src[4] == 'e' && src[5] == 'd' +} + // DecompressVote decodes a compressed vote in src and appends it to dst. // To re-use dst, run like: dst = dec.DecompressVote(dst[:0], src) func (d *StatelessDecoder) DecompressVote(dst, src []byte) ([]byte, error) { + result, err := d.decompressVote(dst, src) + if err != nil && isLikelyUncompressedMsgpack(src) { + return nil, fmt.Errorf("%w: %v", ErrLikelyUncompressed, err) + } + return result, err +} + +// decompressVote performs the actual decompression logic. +func (d *StatelessDecoder) decompressVote(dst, src []byte) ([]byte, error) { if len(src) < 2 { return nil, fmt.Errorf("header missing") } diff --git a/network/vpack/vpack_test.go b/network/vpack/vpack_test.go index 3b6bc5643c..78fe3691aa 100644 --- a/network/vpack/vpack_test.go +++ b/network/vpack/vpack_test.go @@ -23,11 +23,12 @@ import ( "testing" "unsafe" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // checkVoteValid analyzes a vote to determine if it would cause compression errors and what kind. diff --git a/network/websocketProxy_test.go b/network/websocketProxy_test.go index a08c18db1e..f08f397b38 100644 --- a/network/websocketProxy_test.go +++ b/network/websocketProxy_test.go @@ -28,10 +28,12 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + + "github.com/algorand/websocket" + "github.com/algorand/go-algorand/network/addr" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/websocket" - "github.com/stretchr/testify/require" ) var testProxyUpgrader = websocket.Upgrader{ diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 765bdd93dd..9a9694828e 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -38,9 +38,10 @@ import ( "sync/atomic" "time" + "github.com/gorilla/mux" + "github.com/algorand/go-deadlock" "github.com/algorand/websocket" - "github.com/gorilla/mux" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" @@ -671,7 +672,7 @@ func (wn *WebsocketNetwork) Start() error { wn.messagesOfInterestEnc = marshallMessageOfInterestMap(wn.messagesOfInterest) } - if wn.relayMessages { + if wn.relayMessages && wn.config.IncomingConnectionsLimit != 0 { listener, err := net.Listen("tcp", wn.config.NetAddress) if err != nil { wn.log.Errorf("network could not listen %v: %s", wn.config.NetAddress, err) @@ -683,6 +684,8 @@ func (wn *WebsocketNetwork) Start() error { // 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()) + } + if wn.relayMessages { wn.throttledOutgoingConnections.Store(int32(wn.config.GossipFanout / 2)) } else { // on non-relay, all the outgoing connections are throttled. diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 8603fbf4ff..11c11a6d14 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -40,20 +40,19 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/internal/rapidgen" - "github.com/algorand/go-algorand/network/phonebook" - "pgregory.net/rapid" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "pgregory.net/rapid" "github.com/algorand/go-deadlock" "github.com/algorand/websocket" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/internal/rapidgen" "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/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util" @@ -285,6 +284,20 @@ func TestWebsocketNetworkStartStop(t *testing.T) { netA.Stop() } +func TestWebsocketNetworkStartZeroIncomingDoesNotListen(t *testing.T) { + partitiontest.PartitionTest(t) + + netA := makeTestWebsocketNode(t) + netA.config.IncomingConnectionsLimit = 0 + + require.NoError(t, netA.Start()) + defer netA.Stop() + + require.Nil(t, netA.listener) + _, connected := netA.Address() + require.False(t, connected) +} + func waitReady(t testing.TB, wn *WebsocketNetwork, timeout <-chan time.Time) bool { select { case <-wn.Ready(): @@ -341,6 +354,7 @@ func setupWebsocketNetworkABwithLogger(t *testing.T, countTarget int, log loggin readyTimeout := time.NewTimer(5 * time.Second) waitReady(t, netA, readyTimeout.C) t.Log("a ready") + readyTimeout.Reset(5 * time.Second) waitReady(t, netB, readyTimeout.C) t.Log("b ready") @@ -4735,7 +4749,7 @@ func TestWebsocketNetworkHTTPClient(t *testing.T) { require.Equal(t, http.StatusPreconditionFailed, resp.StatusCode) // not enough ws peer headers _, err = netB.GetHTTPClient("invalid") - require.Error(t, err) + require.ErrorContains(t, err, `could not parse a host from url`) } // TestPeerComparisonInBroadcast tests that the peer comparison in the broadcast function works as expected diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go index d3e3cd3fdb..04e7ff91d7 100644 --- a/network/wsPeer_test.go +++ b/network/wsPeer_test.go @@ -27,16 +27,18 @@ import ( "path/filepath" "slices" "sort" + "strconv" "strings" "sync/atomic" "testing" "time" + "github.com/stretchr/testify/require" + "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/metrics" - "github.com/stretchr/testify/require" ) func TestCheckSlowWritingPeer(t *testing.T) { @@ -133,17 +135,18 @@ func TestVersionToMajorMinor(t *testing.T) { require.Equal(t, int64(2), mi) ma, mi, err = versionToMajorMinor("1.2.3") - require.Error(t, err) + require.ErrorContains(t, err, `version 1.2.3 does not have two components`) require.Zero(t, ma) require.Zero(t, mi) ma, mi, err = versionToMajorMinor("1") - require.Error(t, err) + require.ErrorContains(t, err, `version 1 does not have two components`) require.Zero(t, ma) require.Zero(t, mi) ma, mi, err = versionToMajorMinor("a.b") - require.Error(t, err) + var numErr *strconv.NumError + require.ErrorAs(t, err, &numErr) require.Zero(t, ma) require.Zero(t, mi) } diff --git a/node/netprio_test.go b/node/netprio_test.go index 1073c88f75..c4e99f5dc8 100644 --- a/node/netprio_test.go +++ b/node/netprio_test.go @@ -20,9 +20,10 @@ import ( "encoding/base64" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // TestBase64AllocboundSize tests that the base64 encoded size of the Nonce is the same as the allocbound diff --git a/nodecontrol/NodeController.go b/nodecontrol/NodeController.go index 957c9dc39d..1c8ba7f508 100644 --- a/nodecontrol/NodeController.go +++ b/nodecontrol/NodeController.go @@ -17,7 +17,11 @@ package nodecontrol import ( + "context" + "fmt" + "os" "path/filepath" + "strings" "syscall" "time" @@ -111,7 +115,7 @@ func (nc NodeController) stopProcesses() (kmdAlreadyStopped bool, err error) { return } -func killPID(pid int) (killed bool, err error) { +func killPID(pid int, beforeKill func()) (killed bool, err error) { process, err := util.FindProcess(pid) if process == nil || err != nil { return false, err @@ -130,8 +134,42 @@ func killPID(pid int) (killed bool, err error) { } select { case <-waitLong: + if beforeKill != nil { + beforeKill() + } return true, util.KillProcess(pid, syscall.SIGKILL) case <-time.After(time.Millisecond * 100): } } } + +// collectGoroutineStacks fetches goroutine stacks from the node's pprof endpoint +// and saves them to a file in the data directory. +func (nc *NodeController) collectGoroutineStacks() { + algodClient, err := nc.AlgodClient() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to create algod client for goroutine dump: %v\n", err) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + goRoutines, err := algodClient.GetGoRoutines(ctx) + if err != nil { + msg := err.Error() + if strings.Contains(msg, "404 Not Found") { + msg = "pprof most likely disabled, consider setting EnableProfiler=true for further debugging" + } + fmt.Fprintf(os.Stderr, "cannot fetch goroutine stacks: %s\n", msg) + return + } + + dumpFile := filepath.Join(nc.algodDataDir, fmt.Sprintf("goroutine-dump-%s.txt", time.Now().Format("20060102-150405"))) + err = os.WriteFile(dumpFile, []byte(goRoutines), 0600) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to write goroutine dump to %s: %v\n", dumpFile, err) + return + } + fmt.Fprintf(os.Stderr, "goroutine dump saved to %s\n", dumpFile) +} diff --git a/nodecontrol/algodControl.go b/nodecontrol/algodControl.go index e5f3ef06bf..625a4b763e 100644 --- a/nodecontrol/algodControl.go +++ b/nodecontrol/algodControl.go @@ -175,7 +175,7 @@ func (nc *NodeController) StopAlgod() (err error) { algodPID, err := nc.GetAlgodPID() if err == nil { // Kill algod by PID - killed, killErr := killPID(int(algodPID)) + killed, killErr := killPID(int(algodPID), nc.collectGoroutineStacks) if killErr != nil { return killErr } diff --git a/nodecontrol/algodControl_test.go b/nodecontrol/algodControl_test.go index 1cd5849830..92268d50d5 100644 --- a/nodecontrol/algodControl_test.go +++ b/nodecontrol/algodControl_test.go @@ -20,8 +20,9 @@ import ( "errors" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestStopAlgodErrorNotRunning(t *testing.T) { diff --git a/nodecontrol/kmdControl.go b/nodecontrol/kmdControl.go index 2acdd78e80..17c4b0f3f6 100644 --- a/nodecontrol/kmdControl.go +++ b/nodecontrol/kmdControl.go @@ -121,7 +121,7 @@ func (kc *KMDController) StopKMD() (alreadyStopped bool, err error) { kmdPID, err := kc.GetKMDPID() if err == nil { // Kill kmd by PID - killed, killErr := killPID(int(kmdPID)) + killed, killErr := killPID(int(kmdPID), nil) if killErr != nil { return false, killErr } diff --git a/protocol/codec_test.go b/protocol/codec_test.go index 91a83932ab..12c89e2564 100644 --- a/protocol/codec_test.go +++ b/protocol/codec_test.go @@ -23,9 +23,11 @@ import ( "reflect" "testing" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/go-codec/codec" "github.com/stretchr/testify/require" + + "github.com/algorand/go-codec/codec" + + "github.com/algorand/go-algorand/test/partitiontest" ) type TestArray [4]uint64 diff --git a/protocol/codec_tester.go b/protocol/codec_tester.go index 27654bb120..06e62b8151 100644 --- a/protocol/codec_tester.go +++ b/protocol/codec_tester.go @@ -27,12 +27,12 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" "github.com/algorand/go-deadlock" - "github.com/algorand/msgp/msgp" - "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) const debugCodecTester = false diff --git a/protocol/hash_test.go b/protocol/hash_test.go index 459a0160e9..a30b46f477 100644 --- a/protocol/hash_test.go +++ b/protocol/hash_test.go @@ -20,8 +20,9 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" + + "github.com/algorand/go-algorand/test/partitiontest" ) // TestHashIDPrefix checks if any HashID const declared in hash.go is a prefix of another. diff --git a/protocol/tags_test.go b/protocol/tags_test.go index e1a42bb85a..cab4ba1b13 100644 --- a/protocol/tags_test.go +++ b/protocol/tags_test.go @@ -24,8 +24,9 @@ import ( "strconv" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) // getConstValues uses the AST to get a list of the values of declared const diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 97f0a43af7..29a35c3560 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -31,7 +31,6 @@ import ( "github.com/gorilla/mux" "github.com/algorand/go-codec/codec" - "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/agreement" diff --git a/rpcs/healthService_test.go b/rpcs/healthService_test.go index 3b5284c5dd..f42ce3df0a 100644 --- a/rpcs/healthService_test.go +++ b/rpcs/healthService_test.go @@ -22,10 +22,11 @@ import ( "path" "testing" + "github.com/stretchr/testify/require" + "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" ) func TestHealthService_ServeHTTP(t *testing.T) { diff --git a/shared/pingpong/accounts.go b/shared/pingpong/accounts.go index 39ca5ead00..648894c6f0 100644 --- a/shared/pingpong/accounts.go +++ b/shared/pingpong/accounts.go @@ -254,7 +254,9 @@ func (pps *WorkerState) integrateAccountInfo(addr string, ppa *pingPongAccount, for _, ap := range *ai.CreatedAssets { assetID := ap.Index pps.cinfo.OptIns[assetID] = uniqueAppend(pps.cinfo.OptIns[assetID], addr) - pps.cinfo.AssetParams[assetID] = ap.Params + if ap.Params != nil { + pps.cinfo.AssetParams[assetID] = *ap.Params + } } } // assets held @@ -273,7 +275,9 @@ func (pps *WorkerState) integrateAccountInfo(addr string, ppa *pingPongAccount, for _, ap := range *ai.CreatedApps { appID := ap.Id pps.cinfo.OptIns[appID] = uniqueAppend(pps.cinfo.OptIns[appID], addr) - pps.cinfo.AppParams[appID] = ap.Params + if ap.Params != nil { + pps.cinfo.AppParams[appID] = *ap.Params + } } } // apps opted into @@ -436,7 +440,9 @@ func (pps *WorkerState) makeNewAssets(client *libgoal.Client) (err error) { pps.cinfo.OptIns[assetID] = uniqueAppend(pps.cinfo.OptIns[assetID], addr) _, has := pps.cinfo.AssetParams[assetID] if !has { - newAssets[assetID] = ap.Params + if ap.Params != nil { + newAssets[assetID] = *ap.Params + } } } } @@ -807,7 +813,9 @@ func (pps *WorkerState) prepareApps(client *libgoal.Client) (err error) { for _, ap := range *ai.CreatedApps { appID := ap.Id pps.cinfo.OptIns[appID] = uniqueAppend(pps.cinfo.OptIns[appID], addr) - pps.cinfo.AppParams[appID] = ap.Params + if ap.Params != nil { + pps.cinfo.AppParams[appID] = *ap.Params + } } } diff --git a/shared/pingpong/accounts_test.go b/shared/pingpong/accounts_test.go index c35c032aec..c243c8c207 100644 --- a/shared/pingpong/accounts_test.go +++ b/shared/pingpong/accounts_test.go @@ -20,11 +20,11 @@ import ( "encoding/binary" "testing" + "github.com/stretchr/testify/assert" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" ) func makeKeyFromSeed(i uint64) *crypto.SignatureSecrets { diff --git a/stateproof/verify/stateproof_test.go b/stateproof/verify/stateproof_test.go index 07e3d9d79c..832f8fd903 100644 --- a/stateproof/verify/stateproof_test.go +++ b/stateproof/verify/stateproof_test.go @@ -17,6 +17,10 @@ package verify import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" @@ -26,8 +30,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" - "testing" ) func invokeValidateStateProof(latestRoundInIntervalHdr *bookkeeping.BlockHeader, diff --git a/stateproof/worker_test.go b/stateproof/worker_test.go index de1de63c91..7a08c71fdc 100644 --- a/stateproof/worker_test.go +++ b/stateproof/worker_test.go @@ -30,6 +30,8 @@ import ( "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" @@ -46,7 +48,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" - "github.com/algorand/go-deadlock" ) type testWorkerStubs struct { diff --git a/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp b/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp index 7ba924f99d..9d0f3a8d8b 100644 --- a/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp +++ b/test/e2e-go/cli/goal/expect/catchpointCatchupTest.exp @@ -180,7 +180,7 @@ if { [catch { ::AlgorandGoal::StartNode $TEST_ROOT_DIR/Node False $WEBPROXY_LISTEN_ADDRESS # once the node is started we can clear the ::GLOBAL_TEST_ALGO_DIR, so that shutdown would be done as a network. - set ::GLOBAL_TEST_ALGO_DIR "" + unset ::GLOBAL_TEST_ALGO_DIR ::AlgorandGoal::WaitForRound $CATCHPOINT_ROUND $TEST_ROOT_DIR/Node diff --git a/test/e2e-go/cli/tealdbg/cdtmock/main.go b/test/e2e-go/cli/tealdbg/cdtmock/main.go index 77a889661d..261b88e13a 100644 --- a/test/e2e-go/cli/tealdbg/cdtmock/main.go +++ b/test/e2e-go/cli/tealdbg/cdtmock/main.go @@ -18,7 +18,6 @@ package main import ( "fmt" - "net/http" "os" "strings" diff --git a/test/e2e-go/features/accountPerf/sixMillion_test.go b/test/e2e-go/features/accountPerf/sixMillion_test.go index 1ed8e1cb4e..03989a987a 100644 --- a/test/e2e-go/features/accountPerf/sixMillion_test.go +++ b/test/e2e-go/features/accountPerf/sixMillion_test.go @@ -29,9 +29,10 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" clientApi "github.com/algorand/go-algorand/daemon/algod/api/client" @@ -966,7 +967,7 @@ func scenarioD( } pass := checkApplicationParams( appCallFields[(*app.Params.GlobalState)[0].Value.Uint], - app.Params, + *app.Params, baseAcct.pk.String(), &globalStateCheck, &globalStateCheckMu) diff --git a/test/e2e-go/features/incentives/suspension_test.go b/test/e2e-go/features/incentives/suspension_test.go index 965b837c98..cef9ef432d 100644 --- a/test/e2e-go/features/incentives/suspension_test.go +++ b/test/e2e-go/features/incentives/suspension_test.go @@ -22,9 +22,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/config" "github.com/stretchr/testify/require" + "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" "github.com/algorand/go-algorand/libgoal" diff --git a/test/e2e-go/features/incentives/whalejoin_test.go b/test/e2e-go/features/incentives/whalejoin_test.go index 082b803f83..bad75c7536 100644 --- a/test/e2e-go/features/incentives/whalejoin_test.go +++ b/test/e2e-go/features/incentives/whalejoin_test.go @@ -21,9 +21,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/config" "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/basics" diff --git a/test/e2e-go/features/multisig/multisig_test.go b/test/e2e-go/features/multisig/multisig_test.go index 87d94adebb..5ffe729f7c 100644 --- a/test/e2e-go/features/multisig/multisig_test.go +++ b/test/e2e-go/features/multisig/multisig_test.go @@ -20,11 +20,12 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // create a 2 out of 3 multisig address diff --git a/test/e2e-go/features/p2p/p2p_basic_test.go b/test/e2e-go/features/p2p/p2p_basic_test.go index 2f5614ee5d..24de2254f6 100644 --- a/test/e2e-go/features/p2p/p2p_basic_test.go +++ b/test/e2e-go/features/p2p/p2p_basic_test.go @@ -22,11 +22,12 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func testP2PWithConfig(t *testing.T, templateName string) *fixtures.RestClientFixture { diff --git a/test/e2e-go/features/privatenet/privatenet_test.go b/test/e2e-go/features/privatenet/privatenet_test.go index a5bbe3a542..13b71eebc4 100644 --- a/test/e2e-go/features/privatenet/privatenet_test.go +++ b/test/e2e-go/features/privatenet/privatenet_test.go @@ -20,9 +20,10 @@ package privatenet import ( "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) // TestPrivateNetworkImportKeys tests that part keys can be exported and diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 0f193a3916..ceba57c879 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -291,7 +291,10 @@ func TestAssetConfig(t *testing.T) { for _, asset := range *info.CreatedAssets { idx := asset.Index cp := asset.Params - assets = append(assets, assetIDParams{idx, cp}) + if cp == nil { + continue + } + assets = append(assets, assetIDParams{idx, *cp}) a.Equal(derefString(cp.UnitName), fmt.Sprintf("test%d", cp.Total-1)) a.Equal(derefString(cp.Name), fmt.Sprintf("testname%d", cp.Total-1)) a.Equal(derefString(cp.Manager), manager) @@ -978,7 +981,9 @@ func TestAssetCreateWaitRestartDelete(t *testing.T) { var asset model.AssetParams var assetIndex basics.AssetIndex for _, cp := range *info.CreatedAssets { - asset = cp.Params + if cp.Params != nil { + asset = *cp.Params + } assetIndex = cp.Index } @@ -1000,7 +1005,9 @@ func TestAssetCreateWaitRestartDelete(t *testing.T) { a.NotNil(info.CreatedAssets) a.Equal(len(*info.CreatedAssets), 1) for _, cp := range *info.CreatedAssets { - asset = cp.Params + if cp.Params != nil { + asset = *cp.Params + } assetIndex = cp.Index } verifyAssetParameters(asset, "test", "testunit", manager, reserve, freeze, clawback, @@ -1079,7 +1086,9 @@ func TestAssetCreateWaitBalLookbackDelete(t *testing.T) { var asset model.AssetParams var assetIndex basics.AssetIndex for _, cp := range *info.CreatedAssets { - asset = cp.Params + if cp.Params != nil { + asset = *cp.Params + } assetIndex = cp.Index } @@ -1103,7 +1112,9 @@ func TestAssetCreateWaitBalLookbackDelete(t *testing.T) { a.NotNil(info.CreatedAssets) a.Equal(len(*info.CreatedAssets), 1) for _, cp := range *info.CreatedAssets { - asset = cp.Params + if cp.Params != nil { + asset = *cp.Params + } assetIndex = cp.Index } verifyAssetParameters(asset, "test", "testunit", manager, reserve, freeze, clawback, diff --git a/test/e2e-go/features/transactions/logicsig_test.go b/test/e2e-go/features/transactions/logicsig_test.go index 4a03e928d4..fd6ac53b17 100644 --- a/test/e2e-go/features/transactions/logicsig_test.go +++ b/test/e2e-go/features/transactions/logicsig_test.go @@ -20,11 +20,12 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/txntest" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestLogicSigSizeBeforePooling(t *testing.T) { diff --git a/test/e2e-go/features/transactions/proof_test.go b/test/e2e-go/features/transactions/proof_test.go index ad95f8ef35..ff8a86c358 100644 --- a/test/e2e-go/features/transactions/proof_test.go +++ b/test/e2e-go/features/transactions/proof_test.go @@ -20,13 +20,14 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" "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" ) // TxnMerkleElemRaw this struct helps creates a hashable struct from the bytes diff --git a/test/e2e-go/features/transactions/sendReceive_test.go b/test/e2e-go/features/transactions/sendReceive_test.go index 7c38c12a88..d6e51d4715 100644 --- a/test/e2e-go/features/transactions/sendReceive_test.go +++ b/test/e2e-go/features/transactions/sendReceive_test.go @@ -24,7 +24,6 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" - "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" ) diff --git a/test/e2e-go/restAPI/helpers.go b/test/e2e-go/restAPI/helpers.go index 7dec672cf2..e143be2c24 100644 --- a/test/e2e-go/restAPI/helpers.go +++ b/test/e2e-go/restAPI/helpers.go @@ -22,10 +22,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/test/framework/fixtures" - "github.com/stretchr/testify/require" ) // helper generates a random Uppercase Alphabetic ASCII char diff --git a/test/e2e-go/restAPI/other/appsRestAPI_test.go b/test/e2e-go/restAPI/other/appsRestAPI_test.go index 7cb209d9ee..cd9077a87d 100644 --- a/test/e2e-go/restAPI/other/appsRestAPI_test.go +++ b/test/e2e-go/restAPI/other/appsRestAPI_test.go @@ -26,6 +26,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/daemon/algod/api/client" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" @@ -33,11 +35,9 @@ import ( "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/libgoal" + helper "github.com/algorand/go-algorand/test/e2e-go/restAPI" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" - - helper "github.com/algorand/go-algorand/test/e2e-go/restAPI" ) func TestPendingTransactionInfoInnerTxnAssetCreate(t *testing.T) { diff --git a/test/e2e-go/restAPI/other/misc_test.go b/test/e2e-go/restAPI/other/misc_test.go index aa4e72750f..6ff1e25822 100644 --- a/test/e2e-go/restAPI/other/misc_test.go +++ b/test/e2e-go/restAPI/other/misc_test.go @@ -22,12 +22,13 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/daemon/algod/api/client" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/tokens" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestDisabledAPIConfig(t *testing.T) { diff --git a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go index 47903fa7b8..a752638f50 100644 --- a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go +++ b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go @@ -25,6 +25,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/daemon/algod/api/client" @@ -36,11 +38,9 @@ import ( "github.com/algorand/go-algorand/ledger/simulation" "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/protocol" + helper "github.com/algorand/go-algorand/test/e2e-go/restAPI" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" - - helper "github.com/algorand/go-algorand/test/e2e-go/restAPI" ) func TestSimulateTxnTracerDevMode(t *testing.T) { diff --git a/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go b/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go index f86230dc16..ffc1c1c0ca 100644 --- a/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go +++ b/test/e2e-go/restAPI/stateproof/stateproofRestAPI_test.go @@ -21,6 +21,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" @@ -28,12 +30,10 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" + helper "github.com/algorand/go-algorand/test/e2e-go/restAPI" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/db" - "github.com/stretchr/testify/require" - - helper "github.com/algorand/go-algorand/test/e2e-go/restAPI" ) func TestStateProofInParticipationInfo(t *testing.T) { diff --git a/test/e2e-go/upgrades/stateproof_participation_test.go b/test/e2e-go/upgrades/stateproof_participation_test.go index 0049a0bdd8..f0ec77fd50 100644 --- a/test/e2e-go/upgrades/stateproof_participation_test.go +++ b/test/e2e-go/upgrades/stateproof_participation_test.go @@ -21,6 +21,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -28,7 +30,6 @@ import ( "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func waitUntilProtocolUpgrades(a *require.Assertions, fixture *fixtures.RestClientFixture, nodeClient libgoal.Client) { diff --git a/test/framework/fixtures/expectFixture.go b/test/framework/fixtures/expectFixture.go index 218d82ca86..8a7f946d83 100644 --- a/test/framework/fixtures/expectFixture.go +++ b/test/framework/fixtures/expectFixture.go @@ -26,8 +26,9 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) // ExpectFixture is a wrapper for running expect tests diff --git a/test/framework/fixtures/fixture.go b/test/framework/fixtures/fixture.go index 025719a7f9..87d67526bb 100644 --- a/test/framework/fixtures/fixture.go +++ b/test/framework/fixtures/fixture.go @@ -19,8 +19,9 @@ package fixtures import ( "testing" - "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/protocol" ) // TestingTB is identical to testing.TB, beside the private method. diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 38d3b3ec4d..d0aec4625d 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -28,9 +28,10 @@ import ( "testing" "time" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 01ac30c8f6..8c24642f01 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -24,15 +24,13 @@ import ( "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/netdeploy" - "github.com/algorand/go-algorand/daemon/algod/api/client" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" - + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/netdeploy" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/util/tokens" ) diff --git a/tools/block-generator/core/commands.go b/tools/block-generator/core/commands.go index a67fc80e95..a3be48c9d1 100644 --- a/tools/block-generator/core/commands.go +++ b/tools/block-generator/core/commands.go @@ -17,9 +17,10 @@ package core import ( + "github.com/spf13/cobra" + "github.com/algorand/go-algorand/tools/block-generator/generator" "github.com/algorand/go-algorand/tools/block-generator/runner" - "github.com/spf13/cobra" ) // BlockGenerator related cobra commands, ready to be executed or included as subcommands. diff --git a/tools/block-generator/generator/config_test.go b/tools/block-generator/generator/config_test.go index dd8ba9919a..4c71df5971 100644 --- a/tools/block-generator/generator/config_test.go +++ b/tools/block-generator/generator/config_test.go @@ -21,8 +21,9 @@ import ( "os" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestInitConfigFile(t *testing.T) { diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 42665efd11..3fda9f5ffc 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -411,7 +411,7 @@ func (g *generator) WriteAccount(output io.Writer, accountString string) error { nameBytes := []byte(a.name) asset := model.Asset{ Index: a.assetID, - Params: model.AssetParams{ + Params: &model.AssetParams{ Creator: accountString, Decimals: 0, Clawback: &accountString, diff --git a/tools/block-generator/generator/generator_ledger.go b/tools/block-generator/generator/generator_ledger.go index b9e08f9554..14b7edc4ad 100644 --- a/tools/block-generator/generator/generator_ledger.go +++ b/tools/block-generator/generator/generator_ledger.go @@ -25,6 +25,7 @@ import ( "time" "github.com/algorand/avm-abi/apps" + cconfig "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" diff --git a/tools/block-generator/generator/server_test.go b/tools/block-generator/generator/server_test.go index 2c318e0e96..ec1eb1dd8c 100644 --- a/tools/block-generator/generator/server_test.go +++ b/tools/block-generator/generator/server_test.go @@ -21,9 +21,10 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestParseURL(t *testing.T) { diff --git a/tools/block-generator/generator/utils.go b/tools/block-generator/generator/utils.go index e40365a47c..01932a1772 100644 --- a/tools/block-generator/generator/utils.go +++ b/tools/block-generator/generator/utils.go @@ -21,8 +21,9 @@ import ( "fmt" "math/rand" - "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-codec/codec" + + "github.com/algorand/go-algorand/data/basics" ) func weightedSelection(weights []float32, options []interface{}, defaultOption interface{}) (selection interface{}, err error) { diff --git a/tools/block-generator/generator/utils_test.go b/tools/block-generator/generator/utils_test.go index 73148fcb4b..14be5beffd 100644 --- a/tools/block-generator/generator/utils_test.go +++ b/tools/block-generator/generator/utils_test.go @@ -20,9 +20,10 @@ import ( "fmt" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestWeightedSelectionInternalBadInput(t *testing.T) { diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 529629a386..0176b0e1bd 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -19,18 +19,16 @@ package runner import ( "bytes" "context" + _ "embed" // embed conduit template config file "encoding/json" - "io" - "sort" - - // embed conduit template config file - _ "embed" "fmt" + "io" "net/http" "os" "os/exec" "path" "path/filepath" + "sort" "strconv" "strings" "text/template" diff --git a/tools/block-generator/runner/runner.go b/tools/block-generator/runner/runner.go index 06252006a9..e1d2d29f0a 100644 --- a/tools/block-generator/runner/runner.go +++ b/tools/block-generator/runner/runner.go @@ -35,7 +35,7 @@ func init() { Use: "runner", Short: "Run test suite and collect results.", Long: "Run an automated test suite using the block-generator daemon and a provided conduit binary. Results are captured to a specified output directory.", - RunE: func(cmd *cobra.Command, args []string) error{ + RunE: func(cmd *cobra.Command, args []string) error { fmt.Printf("starting block-generator runner with args: %+v\n", runnerArgs) if runnerArgs.Template == "postgres-exporter" && runnerArgs.PostgresConnectionString == "" { diff --git a/tools/block-generator/util/util.go b/tools/block-generator/util/util.go index c447730194..3f909afdf6 100644 --- a/tools/block-generator/util/util.go +++ b/tools/block-generator/util/util.go @@ -24,10 +24,10 @@ import ( "os" "strings" - "github.com/algorand/go-algorand/data/basics" - // import postgres driver _ "github.com/lib/pq" + + "github.com/algorand/go-algorand/data/basics" ) // ErrorNotInitialized is returned when the database is not initialized. diff --git a/tools/debug/algodump/compress.go b/tools/debug/algodump/compress.go index a0aed9566a..3fbebc7b90 100644 --- a/tools/debug/algodump/compress.go +++ b/tools/debug/algodump/compress.go @@ -31,15 +31,17 @@ import ( "time" "github.com/DataDog/zstd" + kzstd "github.com/klauspost/compress/zstd" + "github.com/valyala/gozstd" + + "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/network" "github.com/algorand/go-algorand/network/vpack" "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-deadlock" - kzstd "github.com/klauspost/compress/zstd" - "github.com/valyala/gozstd" ) // Compression-specific flags diff --git a/tools/debug/algodump/compress_bench_test.go b/tools/debug/algodump/compress_bench_test.go index f1ce7c5ece..858d12303b 100644 --- a/tools/debug/algodump/compress_bench_test.go +++ b/tools/debug/algodump/compress_bench_test.go @@ -32,10 +32,11 @@ import ( "testing" "github.com/DataDog/zstd" - "github.com/algorand/go-algorand/network/vpack" - "github.com/algorand/go-algorand/protocol" kzstd "github.com/klauspost/compress/zstd" "github.com/valyala/gozstd" + + "github.com/algorand/go-algorand/network/vpack" + "github.com/algorand/go-algorand/protocol" ) // testCorpus holds all the test data loaded from message files diff --git a/tools/debug/logfilter/main_test.go b/tools/debug/logfilter/main_test.go index 416351e07c..888fd2d620 100644 --- a/tools/debug/logfilter/main_test.go +++ b/tools/debug/logfilter/main_test.go @@ -24,8 +24,9 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestLogFilterExamples(t *testing.T) { diff --git a/tools/debug/transplanter/main.go b/tools/debug/transplanter/main.go index 76782d262c..e6ec587e56 100644 --- a/tools/debug/transplanter/main.go +++ b/tools/debug/transplanter/main.go @@ -34,6 +34,8 @@ import ( "github.com/golang/snappy" _ "github.com/mattn/go-sqlite3" + "github.com/algorand/go-codec/codec" + "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/config/bounds" @@ -46,7 +48,6 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-codec/codec" ) var dataDir = flag.String("d", "", "Data directory to track to get files from") diff --git a/tools/network/bootstrap_test.go b/tools/network/bootstrap_test.go index dee9607a7e..c027bb9aff 100644 --- a/tools/network/bootstrap_test.go +++ b/tools/network/bootstrap_test.go @@ -20,8 +20,9 @@ import ( "context" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestReadFromSRVPriority(t *testing.T) { diff --git a/tools/network/dnssec/anchor_test.go b/tools/network/dnssec/anchor_test.go index 03b01a20a0..60ea899064 100644 --- a/tools/network/dnssec/anchor_test.go +++ b/tools/network/dnssec/anchor_test.go @@ -19,8 +19,9 @@ package dnssec import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestParseRootTrustAnchor(t *testing.T) { diff --git a/tools/network/dnssec/client_test.go b/tools/network/dnssec/client_test.go index 0a785ba06b..3dee975126 100644 --- a/tools/network/dnssec/client_test.go +++ b/tools/network/dnssec/client_test.go @@ -21,9 +21,10 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/miekg/dns" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestEmptyClient(t *testing.T) { diff --git a/tools/network/dnssec/config_test.go b/tools/network/dnssec/config_test.go index f18886a3cb..c97060a458 100644 --- a/tools/network/dnssec/config_test.go +++ b/tools/network/dnssec/config_test.go @@ -20,8 +20,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestConfigSystem(t *testing.T) { diff --git a/tools/network/dnssec/config_unix_test.go b/tools/network/dnssec/config_unix_test.go index 5481766e4f..9c5960c913 100644 --- a/tools/network/dnssec/config_unix_test.go +++ b/tools/network/dnssec/config_unix_test.go @@ -23,8 +23,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestConfigEmpty(t *testing.T) { diff --git a/tools/network/dnssec/dnssec_test.go b/tools/network/dnssec/dnssec_test.go index bd5b913603..d99fb0e93b 100644 --- a/tools/network/dnssec/dnssec_test.go +++ b/tools/network/dnssec/dnssec_test.go @@ -22,10 +22,11 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/miekg/dns" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/test/partitiontest" ) func TestLookup(t *testing.T) { diff --git a/tools/network/dnssec/sort_test.go b/tools/network/dnssec/sort_test.go index a3ef3bfee8..8938d75670 100644 --- a/tools/network/dnssec/sort_test.go +++ b/tools/network/dnssec/sort_test.go @@ -20,8 +20,9 @@ import ( "net" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestSrvSort(t *testing.T) { diff --git a/tools/network/dnssec/trustchain.go b/tools/network/dnssec/trustchain.go index 8daa1a933c..e952a1c5a1 100644 --- a/tools/network/dnssec/trustchain.go +++ b/tools/network/dnssec/trustchain.go @@ -22,8 +22,9 @@ import ( "strings" "time" - "github.com/algorand/go-deadlock" "github.com/miekg/dns" + + "github.com/algorand/go-deadlock" ) // TrustQuerier wraps Querier and trusted root anchor retrieval for better testability diff --git a/tools/network/dnssec/trustedchain_test.go b/tools/network/dnssec/trustedchain_test.go index 4d7e1b7dfe..3c1987883e 100644 --- a/tools/network/dnssec/trustedchain_test.go +++ b/tools/network/dnssec/trustedchain_test.go @@ -22,9 +22,10 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/miekg/dns" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestTrustChainBasic(t *testing.T) { diff --git a/tools/network/dnssec/trustedzone_test.go b/tools/network/dnssec/trustedzone_test.go index 9e78fa512c..c47bb1e80b 100644 --- a/tools/network/dnssec/trustedzone_test.go +++ b/tools/network/dnssec/trustedzone_test.go @@ -21,9 +21,10 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/miekg/dns" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestTrustedZone(t *testing.T) { diff --git a/tools/network/dnssec/util_test.go b/tools/network/dnssec/util_test.go index 3c14cc993e..2f0e688cef 100644 --- a/tools/network/dnssec/util_test.go +++ b/tools/network/dnssec/util_test.go @@ -19,8 +19,9 @@ package dnssec import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestSplitZone(t *testing.T) { diff --git a/tools/x-repo-types/typeAnalyzer/main.go b/tools/x-repo-types/typeAnalyzer/main.go index a2eec918e1..92bb39a462 100644 --- a/tools/x-repo-types/typeAnalyzer/main.go +++ b/tools/x-repo-types/typeAnalyzer/main.go @@ -24,9 +24,8 @@ package main import ( "fmt" - "os" - xpkg "net/http" + "os" ypkg "time" ) diff --git a/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go b/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go index c51f2709f3..238ffa4c40 100644 --- a/tools/x-repo-types/typeAnalyzer/typeAnalyzer_test.go +++ b/tools/x-repo-types/typeAnalyzer/typeAnalyzer_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 TestEdgeFromLabel(t *testing.T) { diff --git a/tools/x-repo-types/xrt.go b/tools/x-repo-types/xrt.go index 51b5577afa..1ea4f8bdb2 100644 --- a/tools/x-repo-types/xrt.go +++ b/tools/x-repo-types/xrt.go @@ -18,6 +18,7 @@ package main import ( "bytes" + _ "embed" "errors" "fmt" "log" @@ -28,8 +29,6 @@ import ( "text/template" "github.com/spf13/cobra" - - _ "embed" ) //go:embed typeAnalyzer/main.tmpl diff --git a/tools/x-repo-types/xrt_test.go b/tools/x-repo-types/xrt_test.go index 19a3c74c7b..2d28fbb311 100644 --- a/tools/x-repo-types/xrt_test.go +++ b/tools/x-repo-types/xrt_test.go @@ -19,8 +19,9 @@ package main import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type testCase struct { diff --git a/util/condvar/timedwait_test.go b/util/condvar/timedwait_test.go index e0d9393fa5..32be7c254d 100644 --- a/util/condvar/timedwait_test.go +++ b/util/condvar/timedwait_test.go @@ -23,8 +23,9 @@ import ( "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestTimedWaitSignal(t *testing.T) { diff --git a/util/db/initialize_test.go b/util/db/initialize_test.go index c368aef57d..61307621ff 100644 --- a/util/db/initialize_test.go +++ b/util/db/initialize_test.go @@ -22,10 +22,10 @@ import ( "errors" "testing" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) // A few migrations functions to mix and match in tests. diff --git a/util/db/versioning_test.go b/util/db/versioning_test.go index aa3670fdab..a8baee5f7f 100644 --- a/util/db/versioning_test.go +++ b/util/db/versioning_test.go @@ -22,8 +22,9 @@ import ( "os" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func testVersioning(t *testing.T, inMemory bool) { diff --git a/util/execpool/stream_test.go b/util/execpool/stream_test.go index 3c2c1cfb7f..00a7d35673 100644 --- a/util/execpool/stream_test.go +++ b/util/execpool/stream_test.go @@ -23,8 +23,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) // implements BatchProcessor interface for testing purposes @@ -121,8 +122,8 @@ func testStreamToBatchCore(wg *sync.WaitGroup, mockJobs <-chan *mockJob, done <- sv.WaitForStop() } -// TestStreamToBatchBasic tests the basic functionality -func TestStreamToBatchBasic(t *testing.T) { +// TestStream_ToBatchBasic tests the basic functionality +func TestStream_ToBatchBasic(t *testing.T) { partitiontest.PartitionTest(t) numJobs := 400 @@ -196,8 +197,8 @@ func TestStreamToBatchBasic(t *testing.T) { } } -// TestNoInputYet let the service start and get to the timeout without any inputs -func TestNoInputYet(t *testing.T) { +// TestStream_NoInputYet let the service start and get to the timeout without any inputs +func TestStream_NoInputYet(t *testing.T) { partitiontest.PartitionTest(t) numJobs := 1 @@ -227,8 +228,8 @@ func TestNoInputYet(t *testing.T) { wg.Wait() } -// TestMutipleBatchAttempts tests the behavior when multiple batch attempts will fail and the stream blocks -func TestMutipleBatchAttempts(t *testing.T) { +// TestStream_MultipleBatchAttempts tests the behavior when multiple batch attempts will fail and the stream blocks +func TestStream_MultipleBatchAttempts(t *testing.T) { partitiontest.PartitionTest(t) mp := mockPool{ @@ -296,9 +297,9 @@ func TestMutipleBatchAttempts(t *testing.T) { sv.WaitForStop() } -// TestErrors tests all the cases where exec pool returned error is handled +// TestStream_Errors tests all the cases where exec pool returned error is handled // by ending the stream processing -func TestErrors(t *testing.T) { +func TestStream_Errors(t *testing.T) { partitiontest.PartitionTest(t) mp := mockPool{ @@ -370,9 +371,9 @@ func TestErrors(t *testing.T) { sv.WaitForStop() } -// TestPendingJobOnRestart makes sure a pending job in the exec pool is cancled -// when the Stream ctx is cancled, and a now one started with a new ctx -func TestPendingJobOnRestart(t *testing.T) { +// TestStream_PendingJobOnRestart makes sure a pending job in the exec pool is canceled +// when the Stream ctx is canceled, and a new one started with a new ctx +func TestStream_PendingJobOnRestart(t *testing.T) { partitiontest.PartitionTest(t) mp := mockPool{ @@ -428,7 +429,7 @@ func TestPendingJobOnRestart(t *testing.T) { <-mp.asyncDelay <-mp.asyncDelay - // wait for the notifiation from cleanup before checking the TestPendingJobOnRestart + // wait for the notifiation from cleanup before checking the TestStream_PendingJobOnRestart <-mbp.notify require.Error(t, mj.returnError) require.False(t, mj.processed) diff --git a/util/metrics/counter_test.go b/util/metrics/counter_test.go index 9e28a49bd1..03ec665b17 100644 --- a/util/metrics/counter_test.go +++ b/util/metrics/counter_test.go @@ -23,8 +23,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type CounterTest struct { diff --git a/util/metrics/gauge_test.go b/util/metrics/gauge_test.go index 8410e1f101..0aed9f8b69 100644 --- a/util/metrics/gauge_test.go +++ b/util/metrics/gauge_test.go @@ -23,8 +23,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) type GaugeTest struct { diff --git a/util/metrics/metrics_test.go b/util/metrics/metrics_test.go index c382b8e8cf..431e071c87 100644 --- a/util/metrics/metrics_test.go +++ b/util/metrics/metrics_test.go @@ -25,9 +25,11 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/go-deadlock" "github.com/stretchr/testify/require" + + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/test/partitiontest" ) type MetricTest struct { diff --git a/util/metrics/opencensus_test.go b/util/metrics/opencensus_test.go index 849aa5f076..6b8b794f21 100644 --- a/util/metrics/opencensus_test.go +++ b/util/metrics/opencensus_test.go @@ -23,12 +23,12 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" "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 diff --git a/util/metrics/prometheus_test.go b/util/metrics/prometheus_test.go index 1eded61598..203c6d6965 100644 --- a/util/metrics/prometheus_test.go +++ b/util/metrics/prometheus_test.go @@ -24,9 +24,10 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestPrometheusMetrics(t *testing.T) { diff --git a/util/metrics/registry_test.go b/util/metrics/registry_test.go index b8f91a67ed..9d4cc4fe5f 100644 --- a/util/metrics/registry_test.go +++ b/util/metrics/registry_test.go @@ -20,8 +20,9 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestWriteAdd(t *testing.T) { diff --git a/util/metrics/reporter_test.go b/util/metrics/reporter_test.go index add4e1e781..846e90ce72 100755 --- a/util/metrics/reporter_test.go +++ b/util/metrics/reporter_test.go @@ -19,8 +19,9 @@ package metrics import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestParseNodeExporterArgs(t *testing.T) { diff --git a/util/metrics/runtime_test.go b/util/metrics/runtime_test.go index 5949de2853..b04d789972 100644 --- a/util/metrics/runtime_test.go +++ b/util/metrics/runtime_test.go @@ -21,8 +21,9 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestRuntimeMetrics(t *testing.T) { diff --git a/util/metrics/tagcounter_test.go b/util/metrics/tagcounter_test.go index 12dec38933..9cca2e966c 100644 --- a/util/metrics/tagcounter_test.go +++ b/util/metrics/tagcounter_test.go @@ -22,8 +22,9 @@ import ( "sync" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestTagCounter(t *testing.T) { diff --git a/util/rateLimit.go b/util/rateLimit.go index 2bb13af97c..ec7775daed 100644 --- a/util/rateLimit.go +++ b/util/rateLimit.go @@ -26,8 +26,9 @@ import ( "sync" "time" - "github.com/algorand/go-algorand/util/metrics" "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/util/metrics" ) var errConManDropped = errors.New("congestionManager prevented client from consuming capacity") diff --git a/util/rateLimit_test.go b/util/rateLimit_test.go index 16d28ff020..c544781af0 100644 --- a/util/rateLimit_test.go +++ b/util/rateLimit_test.go @@ -20,8 +20,9 @@ import ( "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/assert" + + "github.com/algorand/go-algorand/test/partitiontest" ) type mockClient string diff --git a/util/s3/s3Helper_test.go b/util/s3/s3Helper_test.go index 5303f6628b..b29bddfef1 100644 --- a/util/s3/s3Helper_test.go +++ b/util/s3/s3Helper_test.go @@ -22,8 +22,9 @@ import ( "reflect" "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestGetS3UploadBucket(t *testing.T) { diff --git a/util/set_test.go b/util/set_test.go index 03da2c912e..c33df9241b 100644 --- a/util/set_test.go +++ b/util/set_test.go @@ -19,8 +19,9 @@ package util import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestMakeSet(t *testing.T) { diff --git a/util/util_test.go b/util/util_test.go index 1ad4cbcd67..bfb596f5e3 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -19,8 +19,9 @@ package util import ( "testing" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestGetTotalMemory(t *testing.T) {