From 62ef8033459ede83a8f42ceec766f58314bb68e9 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:34:39 -0500 Subject: [PATCH 01/13] Incorporate deltas in presenting paginated view of assets or applications on a per account basis. Includes necessary logic to apply delta changes on top of data queried against database, calculating that the next token returned does not result in missing any entries. Several test cases were added to cover create/modify/delete combinations between the deltas and persisted view. --- ledger/acctdeltas_test.go | 377 ++++++++++++++++++++++++++++++++++++ ledger/acctupdates.go | 391 +++++++++++++++++++++++++++++++------- 2 files changed, 702 insertions(+), 66 deletions(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 0bf62c2197..59bf286074 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -3675,3 +3675,380 @@ 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 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() + au, _ := newAcctUpdates(t, ml, conf) + + knownCreatables := make(map[basics.CreatableIndex]bool) + + // Round 1: create assets 1000-1004 with params and holdings + // 1000: will have holding modified, then overridden in a second delta round + // 1001: will be deleted in delta + // 1002: will remain unchanged + // 1003: will have params-only modification in delta + // 1004: will have params deleted in delta (holding remains) + { + var updates ledgercore.AccountDeltas + updates.Upsert(testAddr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAssetParams: 5, + TotalAssets: 5, + }, + }) + for assetIdx := uint64(1000); assetIdx <= 1004; assetIdx++ { + updates.UpsertAssetResource(testAddr, 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}, + }) + } + + 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 <= 1004; assetIdx++ { + knownCreatables[basics.CreatableIndex(assetIdx)] = true + } + } + + // Add empty rounds so round 1 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 + // 1005: new creation (not in DB) + updates.UpsertAssetResource(testAddr, basics.AssetIndex(1005), + ledgercore.AssetParamsDelta{ + Params: &basics.AssetParams{Total: 5000, UnitName: "A1005"}, + }, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: 5000}, + }) + // 1001: delete holding + updates.UpsertAssetResource(testAddr, basics.AssetIndex(1001), + ledgercore.AssetParamsDelta{}, + ledgercore.AssetHoldingDelta{Deleted: true}) + // 1000: modify holding (will be overridden by delta round 2) + updates.UpsertAssetResource(testAddr, basics.AssetIndex(1000), + ledgercore.AssetParamsDelta{}, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: 9999}, + }) + // 1003: modify params only (holding unchanged) + updates.UpsertAssetResource(testAddr, basics.AssetIndex(1003), + ledgercore.AssetParamsDelta{ + Params: &basics.AssetParams{Total: 7777, UnitName: "A1003new"}, + }, + ledgercore.AssetHoldingDelta{}) + // 1004: delete params (holding remains) + updates.UpsertAssetResource(testAddr, 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(testAddr, 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(testAddr, 0, 100) + require.NoError(t, err) + require.Equal(t, deltaRound2, rnd) + + // Expected: 1000, 1002, 1003, 1004, 1005. (1001 deleted) + require.Len(t, resources, 5) + + 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: deleted + require.NotContains(t, assetMap, basics.AssetIndex(1001)) + + // 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: new creation from delta + require.Equal(t, uint64(5000), assetMap[basics.AssetIndex(1005)].AssetHolding.Amount) + require.NotNil(t, assetMap[basics.AssetIndex(1005)].AssetParams) + require.Equal(t, uint64(5000), assetMap[basics.AssetIndex(1005)].AssetParams.Total) +} + +// 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() + au, _ := newAcctUpdates(t, ml, conf) + + knownCreatables := make(map[basics.CreatableIndex]bool) + + // Round 1: create apps 2000-2004 with params and local state + // 2000: will have local state modified, then overridden in a second delta round + // 2001: will be deleted in delta + // 2002: will remain unchanged + // 2003: will have params-only modification in delta + // 2004: will have params deleted in delta (local state remains) + { + var updates ledgercore.AccountDeltas + updates.Upsert(testAddr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAppParams: 5, + TotalAppLocalStates: 5, + }, + }) + for appIdx := uint64(2000); appIdx <= 2004; 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 <= 2004; appIdx++ { + knownCreatables[basics.CreatableIndex(appIdx)] = true + } + } + + // Add empty rounds so round 1 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: new creation (not in DB) + updates.UpsertAppResource(testAddr, basics.AppIndex(2005), + ledgercore.AppParamsDelta{ + Params: &basics.AppParams{ApprovalProgram: []byte{0x06, 0x81, 0x01}}, + }, + ledgercore.AppLocalStateDelta{ + LocalState: &basics.AppLocalState{ + Schema: basics.StateSchema{NumUint: 50}, + }, + }) + // 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, 2002, 2003, 2004, 2005. (2001 deleted) + require.Len(t, resources, 5) + + 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: deleted + require.NotContains(t, appMap, basics.AppIndex(2001)) + + // 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: new creation from delta + require.Equal(t, uint64(50), appMap[basics.AppIndex(2005)].AppLocalState.Schema.NumUint) + require.NotNil(t, appMap[basics.AppIndex(2005)].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/acctupdates.go b/ledger/acctupdates.go index 77889289e2..f7e2a373c2 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" @@ -1216,98 +1218,355 @@ 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() + } + }() + + // A delta may modify only part of a resource (e.g. holding but not params), so we + // track exactly which fields were changed, then overlay these onto DB results. + type assetDeltaEntry struct { + holdingDeleted bool + holdingModified bool + holding *basics.AssetHolding + paramsDeleted bool + paramsModified bool + params *basics.AssetParams } - 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]assetDeltaEntry) + numDeltaDeleted := 0 - var arwi ledgercore.AssetResourceWithIDs - if !pd.Creator.IsZero() { - ap := pd.Data.GetAssetParams() + for i := currentDeltaLen; i > 0; { + i-- + delta := &au.deltas[i] + for _, assetRes := range delta.Accts.AssetResources { + if assetRes.Addr != addr || assetRes.Aidx <= assetIDGT { + continue + } + if _, ok := deltaResults[assetRes.Aidx]; ok { + continue + } - arwi = ledgercore.AssetResourceWithIDs{ - AssetID: basics.AssetIndex(pd.Aidx), - Creator: pd.Creator, + entry := assetDeltaEntry{} + if assetRes.Holding.Deleted { + entry.holdingDeleted = true + entry.holdingModified = true + numDeltaDeleted++ + } else if assetRes.Holding.Holding != nil { + entry.holdingModified = true + entry.holding = assetRes.Holding.Holding + } + if assetRes.Params.Deleted { + entry.paramsDeleted = true + entry.paramsModified = true + } else if assetRes.Params.Params != nil { + entry.paramsModified = true + entry.params = assetRes.Params.Params + } - AssetResource: ledgercore.AssetResource{ - AssetHolding: &ah, - AssetParams: &ap, - }, + deltaResults[assetRes.Aidx] = entry } - } else { - arwi = ledgercore.AssetResourceWithIDs{ - AssetID: basics.AssetIndex(pd.Aidx), + } + + retRound := currentDBRound + basics.Round(currentDeltaLen) + + au.accountsMu.RUnlock() + needUnlock = false - AssetResource: ledgercore.AssetResource{ - AssetHolding: &ah, - }, + // 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 + if limit > 0 { + dbLimit += uint64(numDeltaDeleted) + } + + persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(assetIDGT), dbLimit, basics.AssetCreatable) + if err != nil { + return nil, basics.Round(0), err + } + + if resourceDbRound == currentDBRound { + seenInDB := make(map[basics.AssetIndex]bool, len(persistedResources)) + result := make([]ledgercore.AssetResourceWithIDs, 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.AssetIndex + if len(persistedResources) > 0 { + dbMaxID = basics.AssetIndex(persistedResources[len(persistedResources)-1].Aidx) + dbHasMore = uint64(len(persistedResources)) == dbLimit } + + for _, pd := range persistedResources { + assetID := basics.AssetIndex(pd.Aidx) + seenInDB[assetID] = true + + dEntry, inDelta := deltaResults[assetID] + if inDelta && dEntry.holdingDeleted { + continue + } + + ah := pd.Data.GetAssetHolding() + arwi := ledgercore.AssetResourceWithIDs{ + AssetID: assetID, + AssetResource: ledgercore.AssetResource{ + AssetHolding: &ah, + }, + } + + if inDelta && dEntry.holdingModified { + arwi.AssetHolding = dEntry.holding + } + + if !pd.Creator.IsZero() && !(inDelta && dEntry.paramsDeleted) { + arwi.Creator = pd.Creator + ap := pd.Data.GetAssetParams() + arwi.AssetParams = &ap + } + if inDelta && dEntry.paramsModified && !dEntry.paramsDeleted { + arwi.Creator = addr + arwi.AssetParams = dEntry.params + } + + 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, dEntry := range deltaResults { + if seenInDB[assetID] || dEntry.holdingDeleted || !dEntry.holdingModified { + continue + } + if dbHasMore && assetID > dbMaxID { + continue + } + arwi := ledgercore.AssetResourceWithIDs{ + AssetID: assetID, + AssetResource: ledgercore.AssetResource{ + AssetHolding: dEntry.holding, + }, + } + if dEntry.params != nil { + arwi.Creator = addr + arwi.AssetParams = dEntry.params + } + result = append(result, arwi) + } + + slices.SortFunc(result, func(a, b ledgercore.AssetResourceWithIDs) int { + return cmp.Compare(a.AssetID, b.AssetID) + }) + if limit > 0 && 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, basics.Round(0), &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} + } au.accountsMu.RLock() - currentDbRound = au.cachedDBRound - au.accountsMu.RUnlock() + needUnlock = true + for currentDBRound >= au.cachedDBRound && currentDeltaLen == len(au.deltas) { + au.accountsReadCond.Wait() + } } - return data, currentDbRound, nil } -// lookupApplicationResources returns all the application 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. +// 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) (data []ledgercore.AppResourceWithIDs, validThrough basics.Round, err error) { - // Look for resources on disk - persistedResources, resourceDbRound, err0 := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(appIDGT), limit, basics.AppCreatable) - if err0 != nil { - return nil, basics.Round(0), err0 +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() + } + }() + + // A delta may modify only part of a resource (e.g. local state but not params), + // so we track exactly which fields changed, then overlay these onto DB results. + type appDeltaEntry struct { + stateDeleted bool + stateModified bool + localState *basics.AppLocalState + paramsDeleted bool + paramsModified bool + params *basics.AppParams } - data = make([]ledgercore.AppResourceWithIDs, 0, len(persistedResources)) - for _, pd := range persistedResources { - als := pd.Data.GetAppLocalState() + 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]appDeltaEntry) + numDeltaDeleted := 0 + + for i := currentDeltaLen; i > 0; { + i-- + delta := &au.deltas[i] + for _, appRes := range delta.Accts.AppResources { + if appRes.Addr != addr || appRes.Aidx <= appIDGT { + continue + } + if _, ok := deltaResults[appRes.Aidx]; ok { + continue + } - arwi := ledgercore.AppResourceWithIDs{ - AppID: basics.AppIndex(pd.Aidx), - AppResource: ledgercore.AppResource{ - AppLocalState: &als, - }, + entry := appDeltaEntry{} + if appRes.State.Deleted { + entry.stateDeleted = true + entry.stateModified = true + numDeltaDeleted++ + } else if appRes.State.LocalState != nil { + entry.stateModified = true + entry.localState = appRes.State.LocalState + } + if appRes.Params.Deleted { + entry.paramsDeleted = true + entry.paramsModified = true + } else if appRes.Params.Params != nil { + entry.paramsModified = true + entry.params = appRes.Params.Params + } + + deltaResults[appRes.Aidx] = entry + } } - if !pd.Creator.IsZero() { - arwi.Creator = pd.Creator + 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 + if limit > 0 { + dbLimit += uint64(numDeltaDeleted) + } + + persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(appIDGT), dbLimit, basics.AppCreatable) + if err != nil { + return nil, basics.Round(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 + + dEntry, inDelta := deltaResults[appID] + if inDelta && dEntry.stateDeleted { + continue + } + + als := pd.Data.GetAppLocalState() + arwi := ledgercore.AppResourceWithIDs{ + AppID: appID, + AppResource: ledgercore.AppResource{ + AppLocalState: &als, + }, + } + + if inDelta && dEntry.stateModified { + arwi.AppLocalState = dEntry.localState + } - // Only populate AppParams if requested to avoid unnecessary memory allocations - // (app params can be ~50KB each, vs ~500 bytes for asset params) - if includeParams { - ap := pd.Data.GetAppParams() - arwi.AppResource.AppParams = &ap + if !pd.Creator.IsZero() && !(inDelta && dEntry.paramsDeleted) { + arwi.Creator = pd.Creator + if includeParams { + ap := pd.Data.GetAppParams() + arwi.AppResource.AppParams = &ap + } + } + if inDelta && dEntry.paramsModified && !dEntry.paramsDeleted { + arwi.Creator = addr + if includeParams { + arwi.AppResource.AppParams = dEntry.params + } + } + + 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, dEntry := range deltaResults { + if seenInDB[appID] || dEntry.stateDeleted || !dEntry.stateModified { + continue + } + if dbHasMore && appID > dbMaxID { + continue + } + arwi := ledgercore.AppResourceWithIDs{ + AppID: appID, + AppResource: ledgercore.AppResource{ + AppLocalState: dEntry.localState, + }, + } + if dEntry.params != nil { + arwi.Creator = addr + if includeParams { + arwi.AppResource.AppParams = dEntry.params + } + } + result = append(result, arwi) + } + + slices.SortFunc(result, func(a, b ledgercore.AppResourceWithIDs) int { + return cmp.Compare(a.AppID, b.AppID) + }) + if limit > 0 && 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.lookupApplicationResources: database round %d is behind in-memory round %d", resourceDbRound, currentDBRound) + return nil, basics.Round(0), &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} + } au.accountsMu.RLock() - currentDbRound = au.cachedDBRound - au.accountsMu.RUnlock() + 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) { From 618520308db640d9a3a297d0296aaca52e2121e2 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Fri, 13 Feb 2026 20:02:30 -0500 Subject: [PATCH 02/13] Move the account assets and account applications endpoints to the non-participating API space (moving them out from behind the enableExperimentalAPI flag). --- daemon/algod/api/algod.oas2.json | 4 +- daemon/algod/api/algod.oas3.yml | 4 +- .../v2/generated/experimental/routes.go | 545 ++++++-------- .../nonparticipating/public/routes.go | 702 ++++++++++-------- daemon/algod/api/server/v2/handlers.go | 8 - .../server/v2/test/handlers_resources_test.go | 1 - ledger/ledger.go | 3 +- 7 files changed, 626 insertions(+), 641 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index a53a85841d..d65a6c2038 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -282,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.", @@ -329,7 +329,7 @@ "/v2/accounts/{address}/applications": { "get": { "description": "Lookup an account's application holdings (local state and params if the account is the creator).", - "tags": ["public", "experimental"], + "tags": ["public", "nonparticipating"], "produces": ["application/json"], "schemes": ["http"], "summary": "Get a list of applications held by an account.", diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index fec36390d5..8ff72e632f 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -3405,7 +3405,7 @@ "summary": "Get a list of applications held by an account.", "tags": [ "public", - "experimental" + "nonparticipating" ] } }, @@ -3661,7 +3661,7 @@ "summary": "Get a list of assets held by an account, inclusive of asset params.", "tags": [ "public", - "experimental" + "nonparticipating" ] } }, diff --git a/daemon/algod/api/server/v2/generated/experimental/routes.go b/daemon/algod/api/server/v2/generated/experimental/routes.go index aa6d278812..bbccb3065d 100644 --- a/daemon/algod/api/server/v2/generated/experimental/routes.go +++ b/daemon/algod/api/server/v2/generated/experimental/routes.go @@ -8,26 +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 applications held by an account. - // (GET /v2/accounts/{address}/applications) - AccountApplicationsInformation(ctx echo.Context, address basics.Address, params AccountApplicationsInformationParams) 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 // Returns OK if experimental API is enabled. // (GET /v2/experimental) ExperimentalCheck(ctx echo.Context) error @@ -41,81 +32,6 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } -// 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 -} - -// 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 @@ -166,8 +82,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } - router.GET(baseURL+"/v2/accounts/:address/applications", wrapper.AccountApplicationsInformation, m...) - 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...) @@ -176,240 +90,231 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "PZ2ejm7/TwAAAP//ru9VAQYfAQA=", + "H4sIAAAAAAAC/+y9e5PbtrIg/lVQurfKj5+oGTtOzol/deruJHZy5saOXZ5Jzt7r8SYQ2ZJwhgJ4AFCP", + "eOe7b6EBkCAJSpRGdpKt/cseEY9Go9Fo9PPjKBXLQnDgWo2efxwVVNIlaJD4F80yCQr/m4FKJSs0E3z0", + "fHTBCU1TUXJNinKas5TcwnYyGo+Y+VpQvRiNR5wuYfS8GmQ8kvCvkknIRs+1LGE8UukCltROqzVI0/f9", + "RfLf58nXHz5++de70Xikt4UZQ2nJ+Hw0Hm2SuUjcj1OqWKomF278u31faVHkLKVmCQnL4ouqmxCWAdds", + "xkD2Law53q71LRlny3I5en5eLYlxDXOQPWsqikuewaZvUcFnqhTo3vWYjwNW4sc46RrMoDtX0WiQUp0u", + "CsG4jqyE4FdiP0eXEHTftYiZkEuq2+0D8kPaezJ+cn73bxUpPhl/+UWcGGk+F5LyLKnG/bYal1zZdncH", + "NPRf2wj4VvAZm5cSFFkvQC9AEr0AIkEVgisgYvpPSDVhivzn1ZsfiZDkNShF5/CWprcEeCoyyCbkcka4", + "0KSQYsUyyMYkgxktc62IFtizoo9/lSC3NXYdXCEmgRtaeD/6pxJ8NB4t1byg6e3oQxtNd3dmyDQvM+iu", + "69J+IDTLmPmJ5oRpWCrCeGOBE/KTAvIrcif1q4HWDUlmZZ43jm3NwcjDeS6mNCdKUw1jYmEfE9Dp5NGE", + "vC5zzYocyIrmJSiSUk6mQFKxXNJEgRlHG6S9CHAkQZeSMz4ngufbxryXLxShPCO5SP2UBpuwKXJhlj6j", + "uYI4dj16QvQiGkI827VH8Fv9QKWkW/O30tvc75r5O2dLFiGq13RjDjTh5XIKkoiZQXdzpX30YEcM4d3J", + "EUrG9VfP2myg/nVJN13wrmXJU7MFAYBaUq5oaloglBlTRU63SNlLuvnb+dgBrgjNc1IAz8xm6Q1XfUsx", + "c59sIRw2EURfL4CYL6SgcwjwbKla+69a3AKvDieZbvFTIWHFRKmqTj3rwKkjCwmOoRQlj90TBD84NPdc", + "EbbvKe+Hdzji3e5vis3dpzbUV2x+vS2AzFiOh/2fpdIVAZcKt30BRBWQmqsvI2YYg3zF5pzqUsLzG/7Y", + "/EUScqUpz6jMzC9L+xOyhys2Nz/l9qdXYs7SKzbv2YEK1hibVNhtaf8x48U5pd5Er/JXQtyWRbigNDwL", + "hlYuX/RRhh2znzTi99NFJbbh/rixrjeXL/putN099KbayB4ge3FXUNPwFrYSDLQ0neE/mxmSFp3J30ZW", + "ujO9dTGLodaQv7tMkK1eWPH1oubg79xn8zUVXIOVRAIef4Z33fOPoeAqRQFSMzsoLYoE+X+C/N/89O8S", + "ZqPno387q+XsM9tdnQWTvzK9rrCTkYUkGMaX0KI4YIy39oboP+iGD9mjPhOSrBcsXRC9YOa2tZuIYq/h", + "NDmsKNeT0UEn+S7kDu8dEPVWWBnFbkWLAfXuBbENp6CQ9t2b44Fq3LzBjYs3cHjrk4cXRVEjF79fFIVF", + "1ZiwGQGG4hRsmNLqEWKG1oesecNPyPfh2GuW51YQmIK7dyAzY1q+7fi4e/8YxOIa6hEfKII7LeTE7FoX", + "Deqy3pjTkGf1YJGgRClT+6ESNnZSWmyXcIyYDGKuowRvtC4d/qTAkmBB54zjUGMj13KypLeGcVMucFMM", + "OYGqBFZLrPaaXDO9qK/OSuibkOvmdeqwjr80N9PID6UCQm2LGhaSllIJORlFRK0//cmKkRQx9ESZEZpI", + "zpQ2t2SIq4pW7Omo3v4NqjWPuVPQKD5FFyI3gttekjSN/+7ahnzT/D6o85+eZ4Zo7+eWqAZwSEUeaH8J", + "30otVtjlhNjD8MCLdt/j+KAZpYcDmk+n5n0hXR3O9FqE9kfhdv8Xsai+PY8yJ2xMFpDjMynOkY4imgG0", + "sGMRFcxrSQtL5u6LfX0wTmit00BY7yl/DhQNozCHus4a7wjV0cx8L8ONQmK1lE0YvslFevt3qhYnOPxT", + "P1b3WOA0ZAE0A0kWVC0iZ6pF2/VoQ+jbNESaJdNgqkm1xFdirk6wxFwcwtWK4lua52bqLjdrrRYHHnSQ", + "85yYxgSWTGtzAVgd3pytgFvWMyEvabowsgVJaZ6Pa2WmKJIcVpATIQnjHOSY6AXV9eHHkf3zHs+RV9GR", + "YDVOEYpSoISZkKhekUCWFC+npdf5hX0q5qroEtpCorksRakNjMF7+/KFXx2sgCNPqoZG8Ks1opoqHHxi", + "5nafcGYu7OKoBNTOOm1g1tWBhkCb1vVVy+sphMxQO0y1+Y1Jkgpph7CXv5vc/AeorDtb6nxYSEjcEJKu", + "QCqam9W1FvWoIt9Tnc49JzOjmgYn01FhXA9hOQf2Q6EQZEQn96Zw+mbz2Qg4hpJq6mEop6BMU+0H3tkG", + "VXYm08DwLS3I0irbSUHT24Og/LaePM5mBp28l1a/77bQLaLaoesNy9SptgkH69ur5gmxmkrPjvYorWNr", + "t3MNQcC1KIhlHy0QLKfA0SxCxObk19o3YhOD6Rux6VxpYgMn2QkzzmBm/43YvHCQCbkf8zj2EKSbBXK6", + "BIW3W8N2amap7VsXUyGPkyY69szaakeoGTUQpsYtJGHTskjc2YzY1GyD1kCkUoruFgLaw8cw1sDClaaf", + "AAvKjHoKLDQHOjUWxLJgOZyA9BdRIW5KFXzxlFz9/eLLJ09/efrlV4YkCynmki7JdKtBkYdOO03QOPYo", + "+nBC6SI++lfPvBW1OW5sHKssWdKiO5S1ztqHsW1GTLsu1ppoxlVXAA7iiGCuNot28s72uxuPXsC0nF+B", + "1uYR/FaK2cm5YWeGGHTY6G0hjWChmpZsJy2dZabJGWy0pGcFtgSeWXu9WQdT5g24nJ6EqPo2PqtnyYjD", + "KJppdx+KQ7epnmYbbpXcyvIUmg+QUsjoFVxIoUUq8sTIeUxEdBdvXQviWvjtKtq/W2jJmipi5kazbcmz", + "HhWF3vDh95cd+nrDa9zsvMHseiOrc/MO2Zcm8utXSAEy0RtOkDobmpOZFEtCSYYdUdb4HrSVv9gSrjRd", + "Fm9ms9PoSAUOFFHxsCUoMxOxLYz0oyAVPFN7tTneht1CpptqCM7a2PIWWN0PlUPT1ZanqEY6xVnu1345", + "AzVRW54GqjADYw7ZvEGrn1Tl1YcpC8UDFYHUYOoVfkY71gvINf1OyOta3P1eirI4OTtvzzl0OdQtxlnK", + "MtPXa5QZn+fQkNTnBvZJbI2/y4K+rZQOdg0IPRLrKzZf6OB9+VaKT3CHRmeJAYofrHIpN326KqYfRWaY", + "jy7VCUTPerCaIxq6DfkgnYpSE0q4yAA3v1RxobTH1c8c1LSUErgO5VzUZzBFpmCoK6WlWW1ZEC1i90vd", + "MaGpPaEJokb1OOdUDka2lZ1uQVdAaC6BZlsyBeBETM2ia98cXCRVpDCysxPrnEg8lN82gC2kSEEpyBKn", + "z94Lr29n7x+9A3m4GlxFNQtRgsyo/DQruF3tBf4Wtolzvnv4w8/q0R9lEVpomu/ZAmwT24i2+q67lHvA", + "tIuI2xCFpGy1hfYkGBHbMJ0cNPQh+/7Y693+NpgdIvhECFyBRD+wT3q0/CSfgCgr+D/xwfokSyiLxIiB", + "veoHI7ma/eaUCy8b7pmhmiCnSif7rhTTqKE3MUsNuHjsFsGBe+TJV1RpFAMJ4xnqb+1ViPNY2dJMMTrQ", + "FRKn7H2NmUl/9g+x7rSpud65KlX1KlNlUQipIYstD23WvXP9CJtqLjELxq6eftZ9Zt/IfQgMxnd4dIoA", + "/IPqykLtbN7dxaHXgRFftodiuQFfjaNdMF75VgHiQ0/8HhiZqvfAkhtTLXqbCpED5dZvWxSF4VA6KXnV", + "rw+DV7b1hf6pbtslSWsGspJKJkChicm1d5CvLdKt+/qCKuLg8P4JqPCyjp1dmM2xThTjKSS7zgs+gk2r", + "8OAcddzLYi5pBkkGOd1GvC3sZ2I/H0gYfmwkkFp/IDQkU7QmxmmkPhPeS/q4WQVOpWKCN8EvJDXn3Dyj", + "alJzvY+fNAOcNsY3HbE+qGZBMKJ04MdDZFl6ioyId/9KaENWjuhwNe5WuudaerBXzfpJEIjjJrUioD37", + "f4Fyc1cC2Enn34LqW3g99amW3aP+x7u9cWG2rrLWbRO9Inr58h7G2MeDemwRb6nULGUFPld/gO3JX+/t", + "CaK+EiQDTVkOGQk+2Jd8EfYn1nm+PeZxr/lB6tYu+B19a2Q53jOrCfwtbFFt8tbG4QTaqlOoIyKjmguX", + "coKA+lgP8+IJm8CGpjrfOr/fLVmDBKLKqfVa6ZrQtCiScIB4oGX/jM4gHzWH7/QQuMKhguXFPA/ta2s3", + "fNetJ1cDHe6VVQiRR/Sf7RPfQUYUgkHuQqQQZtcZzfMt0VWwl6ekBpDugkBvjEqeeaAaaMYVkP8SJUby", + "GcouNVRCmpAo+aCwbGYw4mY1p3NVrTEEOSzBvubxy+PH7YU/fuz2nCkyg7V1ueHYsI2Ox49RFfdWKN04", + "XCfQdpvjdhm5dNBWiWGKzgm3xVP2O7m5kYfs5NvW4JWB05wppRzhmuXfmwG0TuZmyNpDGhnm4IfjDjLf", + "NV3COuvGfb9iyzKn+hSGSljRPBErkJJlsJeTu4mZ4C9XNH9Tdbsbj2ADqaHRFJIUQ4sHjgXXpo+NRh5h", + "YC8zB9iGOw0FCC5tryvbac9Lu/ZbZsslZIxqyLekkJCCje00UqqqljohNtAnXVA+xxeQFOXcuTrbcZDh", + "l8pqwmTJO0McKorpDU/QhKGiwZVotvQh2kYIA2petm37h32srWkFir2MBl3awfa07UFRk+l41PvwN/he", + "1Q9/i7dmnPmxxsSGfBggrYZmoPUM8WlkpS4Sw200h88Qw6ex0tRDx6DsThw4hdcf+/zCr8qiyLcnEJLs", + "QERCIUHhlRaqAZX9KmbkNUuluMjnorrz1FZpWHaNN7brLz3H9d0xL2DBc8YhWQoOkSf9G/z6Gj8OVjva", + "a7hnRBSIDhqw/fBpIKG1gObkQ0j6vpuEJNM++21Lp/pOyFNZ2e2Ag98UAyzXe9063JTH2tdpnkdM0lb9", + "0OEialw5hTNJqFIiZSgoXmZq7LzPrRXburW30P+2Co06wQFuj9uyvQZhWFaRD3lBKElzhmp+wZWWZapv", + "OEVNX7DUiLOgVw70q4W/9U3ieuiImtgNdcMpOopW+r+oY9AMInqo7wC8dliV8zko3XpgzQBuuGvFOCk5", + "0zjX0hyXxJ6XAiR67E1syyXdkpmhCS3IbyAFmZa6+eRYlkoTpVmeO0OwmYaI2Q2nmuRAlSavGb/e4HDe", + "j8QfWQ56LeRthYXJcMY1Bw6KqSTu6fi9/YpBJQ4nCxdggrEW9rP3eK4TyozM2huZbv7Xw/94/v4i+W+a", + "/HaefP3/nX34+Ozu0ePOj0/v/va3/9386Yu7vz36j3+PbZ+HPZbCwEF++cK90S9f4EMsiBNpw/5HMMgs", + "GU+iRBk6FLVokTzEJDuO4B419X56ATdcb7ghvBXNWWZ40cnIp31NdQ60PWItKmtsXEuN5xFw4HPoHqyK", + "RDhVi79+EnmuPcFOh5twy1sxBo4zqpMD6AaOwdWeM+ZW++D7l9fkzBGCeoDE4oYOEmJEXjAugrHh5WN2", + "KQzsuuE3/AXM8D0o+PMbnlFNz+xpOisVyG9oTnkKk7kgz31Q5Auq6Q3vXEO9WeeCoOYg7VyMU9BlfC03", + "N+9pPhc3Nx86fghd2cpNFXJRd866ajI/ZWLkBlHqxKUeSiSsqYzZQnxiGhcNjb13wmFlElFaJZZPbeTG", + "nwyFsihUO0VJF0VFkRsUBaSqXJYNs61EaVEFjhlm7mJvDQ38KJxTiaRr/+QtFSjy65IW7xnXH0hyU56f", + "f4EheHVijl8dDzR0uy1g8MO3N4VK+72LC7dyOTqVJwWdx2wmNzfvNdACKQQFjiW+NPOcYLdmqjEXCYBD", + "1QuoYpEP2BIL2cFxvbjcK9vL5wKMLwo/4aY2Y6fvtYNBVPzRG7gnsp6WepEYjhBdlTLHwO+VTzBA5+bK", + "8R4Eis3xAaAWojRLBpIuIL116fBgWejtuNHdO7q4u9gzHKZQZ+SCA2fM4M+ljCuLjDpBhvJtOzGTssEQ", + "OOg7uIXttbDdJwNTCgYpLIPEQKrv6CLtBndtM5mHS7gAnc13flc+RtQl0cG4S08Wzyu68H36j/Zbl6rv", + "3sc6RhSNPB99iKAygghL/D0oOGKhZrx7kX5seYynwDVbQQI5m7NpHmHT/+jaNTyshiolpMBWPqq3GlAR", + "NiPmdTS117F7MUnK52AudXMRC0VzdNqfRA39KB0ugEo9Bap36mt5mGbCQ4cC+RqDplFpMjZLgI3Zb6ZR", + "CcJhbR54+Pa2bZwj8eQodyq7JsiOBNV3r4OkJ8c8IhzCI1kY/X1f7Un1XnD+aSF1Isj2+9LgcC7F2uym", + "AVD4fK+Y4CW4p0pF5zD0OmqYigamxGhYgHCQfdJPVN4Rs7ZY05ExBi7Cdk8MXqLcAcwXwx7QDNBycfRz", + "WxOisyq8CfJ9TnMUqCsHUUs6VDbsbHx+GLBxNgaS18KqB6yJtfDoL6jyRz8bBxz9SGnx90klsyvr42Xg", + "fUd1N6ejv6bbrH1s9TlTIIKbHj73o0/46LM8jsYHZWwcj1yIQ2zvBEcpOoMc5hYntrGnszo/U72bBo43", + "sxkyvSTmyBcoIwPJxM0B5iH2mBCrMSeDR4idggBstKzjwORHER52Pj8ESO7yS1E/Nt5dwd8QDxa03vhG", + "ShaFufVZj9Uq9SyFNlMCq7aLMw5DGB8Tw0lXNDec1AWe1oN0Mgzi26eVT9D5djzqexMNPGhujSidHLRK", + "K88cs75Q8PbLiL8KDlrDVGwSGxkdfVpNN1NzJqLxChinHTu8Nt/jA0WmYoM+RXjDWQf3g6Hrh8wDFriB", + "bJhCKsd+fWKjBe8wQHYL8jFqVkh6Tq9WkV2fJHscMD3idB/ZPQxS6J0IpJYCs64d4DQ6e/UsTWmrK4nU", + "1+24ymlchanFWE3f4YzuZA9Gu8rT8WhHOs0+FVyk7aAEqT5/I3nYTpVqk4p7/6eAVyOrsJn59uVE7erv", + "Tp0Ft1fpbxX+VV4eD7/nf1qYt4Wnx1by1YOFEnNJRcME/hHEAYS4McIY8mPXM/6eYj0CVhfLhxaOcBnj", + "D08g3DqELNtJwKECp5dyw8vms2Sp7FLlfRKL2s4DERrmFW3zswYQO7D6tv0Ki6K16XnXxGuAtdidaiSV", + "rrW2izYFOaAqK2k8DJPbmF/Fzc17BSj0XvlugaIed4/y7aPAnVPCnCkNtXXMe2l9fuMlMqukkELM+len", + "Czkz63snRCUpW3aKHRvL/OwrwNiLGZNKJ2hajC7BNPpOoSr4O9M0/pJrOowyZW2VB/NMhOgWtknG8jJO", + "yg6kH14YiH6sRC9VTlHSY9y6y02xAEzUw/wA4zrCYyMTdiLolUXQK/o58DPsYJmmBiZpKK85/Z/kiLV4", + "4S7OEqHlGDF1N7QXpTt4bZAMostog1s48Bua7DJads5l5sfe607oU1L0ScF2pOhagpSe8QhYMZ9D5lMV", + "uqhmm7bNJYTMBZ/XyTDN7zvyX06ITUOJWSR3JKB08RXQF13RKKKFksteaQghr8NDMXkmTjIHblMPHSEs", + "5VHEhZEd2CJQ7X9e3t6J+4j6vl+3/N1rp3S7h9Vm4/bkQDOnF1Dg17f70Ha3y6Fu3Oc138hxvPuA4YBI", + "cUyrQIDpEE0P56ZFwbJNy3JtR/0jys91x6ZD/J7CdA/MpYjtnZHuDB9bZ1OxsSzK6YPwSNDUZcnISolW", + "0IaXe/fdVilIBi75h5+vtJB0Ds6SnViQ7jUELucQNASvWkU0s479GZvNILTgqmOsjw3gOna6bAA991Be", + "18xb6UR2kuXBtFWvYD9C4/QUoZQ+X6Hrrh3dvzcCnXB1x7RqpR1oDI8mwvgBtsnPNC/NA4hJVftUO8N2", + "8zY/gCZWyx9giyPvdVU2gO3ZFdR0vAOk0JjGp/qkguz2D1Sj1o0vxtHUaQzcqYv4Lp1oa1zhov6jUV9M", + "DZXUXvXMiY5N7dplIB2yV1dxbylztqC5LW1C37dFQxRAwcsjnIqh19Exd1uVIWavVyTQ3BM+LnZ0Nx7d", + "z0+py8KqEffsxNvqRo7uAnoRW7+VhrPigRtCi0KKFc0T59/VJ2tIsXKyBjb37mCf+VkVPxXXLy9evXXg", + "341HaQ5UJpWGo3dV2K7406zKKqh3X0O2jEClA2YN1Xid6j30AFtjyYCWEq1TWaz29wsOqvMIm8UjHPby", + "TeeaaJe4w0URispDsfaksA6KTadEuqIs9w4LHtqh1iG73GFa/CifCAe4t3NjYFK491iK/QYJukaLHsdC", + "VeHX3YzOlZoZXGIQp8W2xXKbNl5/8+7wze8Nurm5eb/y4NTGSet1WNWXiDimqiPDBjoMMM5A6gO4h20j", + "8t9gWuD4G5C7pMHIrZ33Jj25cPqdkI3b04UIR70/P53Ual44Fo9xD5dr59LSkVUnxMq1v85/NQzr8eOQ", + "4h4/HpNfc/chABB/n7rf8XH3+HHUyyKqdjR8FLWKnC7hURVk1LsRn1clwmE9TIa5WC0rwV30k2FFodaN", + "06N77bC3lszhM3O/WLteFKHdExVuukV3CMyQE3TVF+JbRRIsbUVnRQRvJ7TAkHNDWngfunI41mmle4R4", + "uUQnjkTlLI170PEpckhu/eNNY4KNBztkmDlK1hOkwUsWjG6aqaP8B1oLCWaNIlxF02rX+J0KxwJKzv5V", + "QlBYH6+AlsTg32c4akfqj+s63cDtuv2jY0ru399caYHsRVWv1fdFZYn064/VajswZiicscPzd8T7OELy", + "tyYGhy6c+/1egtr55qwMw1FFkLNEe67pjL79jzVXCNnu4YshG8xUMpPiN4iLDGinjKS/8QZ2hjaA34DH", + "/BLa/KvyvvHrDWffRyDD9Rx9pHJvvYZfdFV58pibO84eDtvoAxUYwX73qzBUPEW/24S+R3PovNUMRuvh", + "YXhgg9AK9LvxLqOU2xNqc8M0ojfj5zwMtj6z49fn3MHcCVDP6XpKY8XCzNvVwBRsf8O5VQviO/sNUlV6", + "Ezs7CeKBqrbMJswsQNYGrG668SPfoXbawS/Q+sGJFBc+NcfWXSZXIjJMydeUoy8u9rMc0PVWYF1BTK+1", + "kJgkV8X9cDNI2TKqmL+5eZ+lXe/JjM3NTL7M8kw7Hyk3ELGZeJGKMqaKnG6rfD4ONZczcj6uz6zfjYyt", + "GD7EsMUT22JKFd7LlVtG1cUsD7heKGz+dEDzRckzCZleKItYJUilK0CJs/Imn4JeA3Byju2efE0eotO9", + "Yit4FL9gnIw2ev7ka/RVtH+cx0SkDGa0zPUuJp8hl/ceaHHKxsgEO4Zhq27UuDfaTAL8Bv33yY7zZbsO", + "OV3Y0l1B+0/XknJqEBKDabkHJtsX9xe9SVp44dZSBEpLsSVMx+cHTQ3H6snIYBiiBYOkYrlkeum8rZVY", + "GgrzrNUfPz+crVTuSgl6uPxHDGMoIk/73+GVRZc9UcIYmfIjmvxDtI4JtVmPc1bHMPkqz+TSZ3fH2oq1", + "6ybixsxllo5iKoY0zUghGdeowSr1LPmrebVLmhqGOOkDN5l+9SxSo7BZxosfBvhnx7sEBXIVR73sIXsv", + "5bi+5CEXPFkajpI9qtOiBKeyN94i7iPf57rfM/S9pWszbtJLgGWDAGnAze9FinzHgPckzmo9B1HowSv7", + "7LRayjjB0NLs0E/vXjlJZClkrFpMzQCcVCJBSwYrjNGOb5IZ8557IfNBu3Af6H9fBzsvlgaimz/d0cdC", + "YOGOvNOq1GRG0v/5dV1jAg3tNva9pbQUMqKedYrGz+wZe5iasG3Ptx6J+K0Hc4PRhqN0sdITMmVjoqo+", + "v4fLWRsku+cNDemTX4k073iU9R8/RqAfPx47UfnXp83Plr0/fjzcazeuJjS/RlBz3F3TzgBr+sa2+hsR", + "Udr5SriV65pL9xNRrEbvMnOlTt0YY9IsN/r55Y7TxPwe7AkdP0AeNfi5jZvfmb/iZtZRZP38oVmBOUo+", + "WfU9COOg5BuxGUpErWvL09MfAEU9KBmoFcSVdCpMR7029rocBWRrRp1CLsxLNSwiN9iD5k+0CwY14x17", + "UbI8+7k2PrduJkl5uoj6tU9Nx1/sMyBoEGgw0gXlHPJob/ta/sW/qiPv/n+KnmGXjMc/tYuZW9hbkNZg", + "NYHwU/rxDa6Yzs0EIYqaSe2qNEH5XGQE56mr/9SscTKKIL5bK7mbJwOHXZbaOUZjAhJXlGfGcnTpjZvB", + "sWUiqe7hqhLD12f1iLAycopVS9jRQRLKlnhtK7oscsBDuAJJ59hVcGh1x6yHOHJQ2oeownzClphASRBd", + "Sk7EbBYsA7hmEvLtmBRUKTvIuVkWbHDu0fMn5+fnw2yLiK8Ba7d49Qt/Uy/uyRk2sV9c9TxbdOQg8I+B", + "/q6mukM2v0tcroTxv0pQOsZi8YNNaoCGYXOv2/LFVantCfkec/wZQm+U2UClqM9S3syrWxa5oNkYE6tf", + "v7x4Reysto8ERB2WT56jBrB5RKJGnuF5hn0Ow578b8PH2Z1+yqxa6aQqbBzLRmpa1PWYWcsTC3WDIXYm", + "5IVVy1b+PHYSgun55RKyoI6yVQMgcZj/aE3TBeo7J6OdKuWeilrDy4B7Dlibi4LQ26roHHJwswxXCdwW", + "Ah8ToRcg10wB5m6BFTSTnlYZg51C3idBba5WlpxbwpkcIL1WJeYO3QUPnBV9vVtFFLLWPtzb9ldnw8Hg", + "/UMLpl/ZXALR0KFW9fWWu4MtO7PxhWsm5LUzdqSUC85SLNgSE8Exnekws+qA2jZxe6caubMcOYbRmu9V", + "kgeHxd4q8J5lXvUkYQi/mv22hGP/1LBxhTTnoJXjgZCNUUHFcnAGOsYVyCo3QSPdtJARj69oiE7lOXJC", + "9/jxCDMS9uhavzPffnS6ecy7dMs46twcUt1L0BrYcsXQzs4J02QuQLnVNkPT1HvTZ3K94QjCh8krMWfp", + "FZvjGNYDEbM3oEdyd6gL75/s/IFN229NW1f/o/q54UlnJ/Xr/hBlIXUSjq5GZMN70R9z+fIRcgFyq/HD", + "0XYQ486wA7yXDRnCCh3+oMD7vEM2IGXs4fnSPFktvWELYoOHo6m3GY+A8Ypxb/CN55JLo3cJbgye5p5+", + "KpVU20fHII53DTTvCc3BuH7rMXDfodrVTAxKcI1+jv5tvN5wV4qlh61UDerXBeVb4g+Foe5AKPmW5pVj", + "vhWmmnppI505Ycz6CNtgXyfexdmKYeuJjw5uoGtvLGrVHSsKHXpP9WXsnZbZHHRCsyyWdOUb/Erwqw9u", + "hA2kZVVIrwp1bZY86FKbmygVXJXLHXP5BvecLmOKKgXLaR7xuH1RfYSs2mFM5jbd4r+xKnL9O+Mc8A8O", + "QPfe9tlhdT66AfUx6dnQdKLYPBmOCbxT7o+OeurjCL3uf1JK97Hnf4jQ8haXC/coxt9emosjTHXfce23", + "V0uViR7d6AV+9zn1qmzITa6EV1mnViJ6ZODmRbasBbxvGAV8RfOepA+h1cber9aS0Zf6Ie3NbEK1ywCp", + "Kal5whAVRn8OPet43bIMdc2bfa7V1rP6UxpPHD52Ir3f0vhDw65ovd5qhtJrTzzO5FcTwaE2P1fOpKsv", + "pXku0sGcwQ1zYTr1p7sWy6WrHhHxylstRRaehdCbCyDO2KzDciSiAh+20W/4tIp+kev4aA39SEU0QzP/", + "IRrdEsY2SNSD54GxU4cTBSpbh1nyHcuxwNp/Xr35cdS/kcEOdLfUpZ+PqrD7NqaKmmuTx1w08LGDBwie", + "x/Xfqkeljump4qfBVfiOfvjOKgiHgGRTNR3S+tXQwTsEMBe2slqs9kw3Qc6o3g6P/IAa6u21HCWkjhhV", + "tCuWRd4+VulZNyFVMd9BxX0bMtKQAmmxWlzupeA1sPaicSnxbIGyTm2zDgN9MUQ47ODjbjy6zA4Sn2L1", + "3EZ2lBiDfcXmC/1NLtLbvwPNQNqaPLHnpK3IswTzDFULVtjMlkKxuqZ2bgZzyfAXONxkaETO9QJcYhqf", + "sKAzlnegXkGqscZ67QYqAYb7ORTxJRoIvEERm/wOriASIINCL3YKS9a5u9CLuvQuuIAzpsgUnOliBXxM", + "2AQm7Ri1rM5LRXKgM6+ElULoAbWpvbbFojEEOkZfnTrnu8XATtq5IKuiLUc9GV7I6KKKCbDxlWuq6uRV", + "rZQOg0PHZzNIsWjEzgyA/1gAD1LCjb3qDmGZBQkBWRUliGVPTqrRrmHdlYtvJ6hBXbdPCWlfco5b2D5Q", + "pEFD0araVWDtMVUUEDnWjusLc+zJgctURU+IIO8H74pY1HXKjimkESTIPBIMT+PmeqqTZh4HjZdojgDD", + "dD1w0t6MfCiY9iUYfGuTTwdXef9L+QVoynLlnEppVbIh1CeRy25J87Ur+YC5HitroS/+AMr/5nPE2lly", + "duuqPCHCrG12TWXmW5wkU5+9N1kc6Fk1M6sDo7pePof65dgIxTQXRgBK+gJDm5FKlQvvA2V9resEagj1", + "DKSErLIJ5kJBooUPszog/6gLn9yBPetlfhTeWh79B0QK2xX11iF5VxdjwZKqFOuOUOd8HmKFSFhSA70M", + "CqTE1aD7duhb+93nN/ElMnerV/vwXp2L/VXmfeiduWdamA9P14w44eBg7tVIinKEZpZxDjLxRtx2eRTe", + "zNSJqZ2zMrWiSng2K+314BRoO7hZVKmZdlfZekIFyThuYXtm1T6+cr/f8RBoK0Na0IOc1i2iOKmuWsXg", + "np8EvN83g2ghRJ70WAYvuzVd2ofhlqW3gLlhq8gUIwU/aB4bMwl5iAapymdkvdj6iiVFARyyRxNCLriN", + "DvTuI80qvq3J+QO9a/4NzpqVtkqT00BPbng8zAqrJcl7cj8/zA6e18ebFBh+ec/57SBHzK43vM9Hbo1l", + "lZq1tidD1Rtd/46WCBWQn4UiJkBdWUPwt8gSIu8ogklZguxB6B9AiTMgE5WLmBf+MYljzFBxTIWTIUAa", + "+IDnag2FGzyKAOdktydDrPvsc6CKWVXz4z7JYF1+VcvEVZ9qpD1zNUuTM86EhHBG9DO1uaKryDZMtYz/", + "mTItqdwek7K1iaqYGqoXy3u9JStHyXohtbNkF4d5LtYJsrWkqlAWUweYdqp5bftav3U/c9SnELhdUl+4", + "ZUsWNCOpkBLSsEc8xNtCtRQSklygF2bMsWOmzSNhiXGdnORiTkSRigxsMcE4BfXNVXJOUfaCwJUtigJL", + "O5gywPYJ6HjglOb2tebZBOW1vbU+/OZfmz42fUWdis8uOrEuAj3xBaBcMjiHIdu4C69NG4eJmNpK2biI", + "PGMbpBuQsSM/I1qWMCauhRVIQhLCg08lkCVTyoJS0dKa5Tlmj2CbwKGh8geKo7ZHdr5EP+gVQ4e3ZiYR", + "K1IX5nas0q+EPOAqTMRG9EKKcr4IShRUcPqnuyzdwz4c5SdVok8ihoiaKZ6RpVDaPYvtSPWSaxfQh6ng", + "Woo8byryrJw/d0bf13Rzkab6lRC3U5rePsJHOBe6Wmk29ikV2r679UyylQ9y2EtBb3iC5KH2Z3q37dCr", + "1dHzYN7Z4n4dw8M+TX4A5of9zHW/XeOiu7D2upp8Nv4WuuCEarFkafy4/bm8X3t9VmPcK5pg0Vbytllo", + "sBnygfAeq9yZkHt20QycRksRXxDHI5xbB3Ii818U49vjkhk4HtRzh3b5jhOwkrRXDGwBgJDaRAi6lLb8", + "dyikVQxHzG3iFHRKaQM68MJB37/7wWZGODlQGu4FVMcbuQLwodVgjG0iTOvZPBUb//1RnSnzKODvdlN5", + "g3n0OVVe1aQlrVulT2TVwxHixRB2eiBeYxKM6VA/ROWthAMv/wCAfs/EBgyD/BMPBWNGWY41+HrufdSB", + "jYPnuouxDEb3NVEtJ09p6atpm7FLCS6xkpX+ZdOcWFBDSqJq3tWI8ww2YGO0fgMpbC3scWDOgtyWym5p", + "FESR5LCChsOmy/ZUohTKVuD7qqozyQAKtPi2FW0xT8Sw0mZL++LWngS+bEOwG1XHWMTanSJ7dC1RzdCG", + "J/aYqKFHyUC0YllJG/hTh4ocTV2iOcoRVHWeD4l/Yg6d5ic7gi+aqS58/5go4zHxYRgfOpgFxVG3iwHt", + "9UwuVd+p53HH5DCVWWUowtmyyq5tSbzmG6qga96v1eySfP0SG7hPTPAAsS83kKJU455CkLnHUI/lxOVA", + "QmrnAJl9MJguEW3+AjjhIqgbvqaqesXUyVz9D3ZibMS4e2gfYaOv/Yfvv7MEByOqlWwxXua3Iuv76fh/", + "l5O48yD2jhejEQUulHeHasxTt3t2YANR5hnhZj+N7I91tt0t5rj4mExLP1Cei7UtBB4+UV+At+da6vMm", + "JieWs+pa9n7SY5dnuK0FYUGEyJJuiZD4j3mQ/qukOZttkc9Y8KvCv2pBDQk5A7L1onB+12bi3eLV2APm", + "FTHCT2XXzYaOGQy3NaMEQJuL3FeOE2RJbyHcBnQQsfwz1YZxqnKKSg1zZbe2s4sFt3ifnmlJs1AJgIlm", + "tw3u4POcm97/fx22Gk7l8z8WOU192XdX/67JZ4wwVBGXXsByd5hzl695EvCtAqKVPk1GdoQ29UDWFYv5", + "6SvU1QC7U0a/U6PsXss4pLJ0nXFkR4D4oKWcehdOE8PZWVJYbXjf4sLiy59nd6IZovuWMQT8P9CuNNwr", + "OpFtvshe/3qwyefYhUYingisVg0+FZtEwkztc6SxevCp2NQAq0p3y3gqgSrrd3T5xj1b6wTIjJtntPXa", + "rcyq1SgZzBivWS3jRakjryDMg8y3AcJCawKitcc21ydjGFF0RfM3K5CSZX0bZ06PrU4cFgzyFhTXN6IA", + "qW7k7gBM1S9AjKeu9fNhM3P922KH1ndWacozKrOwOeMkBWmkBrKmW3W8qaqyOuwzVtFAFmpmCwnMVkja", + "FpB866zN9zQkVQDSE1qUBliC0Ek7YgWyiiEtegw/XRj+FJagJd0kuZhj1G/PgXB5rtF0aB+QgqMS3Up3", + "w9bt51HsN9g9DVYgcYxIC5x1yBS7z/0b3Ep8hP7Emd558q2Gsx2GbT2d7cH0SOXzOjzDEkv3PMYi511i", + "pjB63ouqPk2Jpz0INjHqEt3RqvfsIvpXuLQLoQp9eOHMpgtHLD7f6hUS1DeoHQEYoOq4Apo6D7GuIq6j", + "qLBIGbvsBgfq6ax2399LPeChIkW5s96ctnLQMeMcUm10dz6DpBBFkg7xbbVFijJnZHCQNmHsoY/AhNCz", + "7srvRlVluxo50Rr1uw4tuNpbP2yfraxId6kM+pRMPRy9acAQM+RleIStag1jrSpVzNg/zr2xu6lEq5gE", + "oURCWkpUMq/pdn8Ryp7s81d/v/jyydNfnn75FTENSMbmoOqaBq0ijrVrIuNtrdHndUbsLE/HN8FnC7GI", + "89ZLH/ZWbYo7a5bbqjoZcaeE5SHa6cgFEAvO7VbGO2qvcJw6LOKPtV2xRZ58x2Io+PR7JkWex2vKVHJV", + "xPwS263AAGNeIAVIxZQ2jLBpP2W6dspWC1QuYtbwlc0NJXgKXvvsqIDpHl+u2EL6fHqRn2EuBmdzIrAp", + "cserrJ1o17rcO83q91BoRHebKZBCFE60ZzMSgwhjtmQJlV7dqU1Rnx646VbM1jrsxgjROb/HSe+Cu5ew", + "mJHd3L5ZFlzHOb3ZxIh44Q/lEaTZZ93ozzNyDCepDQN/GP4RSZxyMq5RLfdT8Iro+2BHVPhFx2uiShoy", + "CLRugowIeSAAPfHQjaDVIMguyE0urY0BrRHe/NwWP17XZum9kSkIie+wB7wwlrluVwVTOHB+58Teryuk", + "BEv50EcJjeXvC4/2rLe6SIItckoTrUFZtiS6YmEQEK++reLMe14lnXB0KYQm5mWa55EwdqvHwTMVEo55", + "EsgVzT8/1/iOSaUvEB+QvesP3ArDlkMkW1SqkyfkfEUHgRWEKH8WqPhbjK3/B5idjd6ObhZn+O/cgagS", + "orn19p5VFnDgZI1jWseuJ1+RqSv3U0hImWo7FKy9SFPF24JkM+dfCxvdjv29d5mgn4W+x3GYeX8g8mNg", + "ZKs8BxzM9VH/nZlTDweInpYYqXYIJYK/GK8LC7zvuXbuWRrmuFROQeLGA1M5dUvXD10ergMvr1JBd52D", + "b/0GbiMXfr22obnKBleYubl5r6dDEorFq8GY7pjj7CRlYe5fFOazJDizqHRjOEiihFWL3Puy17T8JYM8", + "Dc1dNOJ+T934hUW/GQ0fBbOS2/GqAqgYK+7ZupiNKy8GwU235+SGPyZqQf3bwv359MuvRuMR8HJpFl9/", + "H41H7uuH2Est20TjSutEOh0fUVdN4IEiBd0OCWbfmzonit86U9DnF2mUZtP4m+7vZs/w4eoCEC45snpk", + "L/YGdflz/l8CoJ3E0Dqs1YmxJFmnB6q2Yl+moJ/70uLb1O891T5a3Ldk+V4nuUYhlrvxaG6TlGF1kl9c", + "rbrPu+0egp58gW7p90kDZhETWWtj8mCqIKnbgIIsrlukQgZGXqelZHp7ZfDv1e7sl9tYMqjvq/RMLudX", + "ZYF3sq8Wt8C9j1mdzKlUXrr+XtAcpU/rGMCNzCnyCXlpK4S4a/FvD6Z/gS/++iw7/+LJX6Z/Pf/yPIVn", + "X359fk6/fkaffP3FE3j61y+fncOT2VdfT59mT589nT57+uyrL79Ov3j2ZPrsq6//8sBQugHZAuor/zwf", + "/c/kIp+L5OLtZXJtgK1xQgv2A5i9QQ3bDBMUIlJTvGJhSVk+eu5/+h/+opykYlkP738duXqQo4XWhXp+", + "drZerydhl7M55kBJtCjTxZmfB3NZNt4rby+ruCDr+4c7WtuccFOr/H7m27uXV9fk4u3lpCaY0fPR+eR8", + "8gTzKRbAacFGz0df4E94eha472eYRftMuWI8Z1Xo6N24860obKke82lepQE1fy2A5sgizR9L0JKl/pME", + "mm3d/9WazucgJxgxZn9aPT3zb4+zjy6vzN2ub2ehN9rghmcfG1l8sj1TeMerfU3OPrrENnsGbNR8dw6x", + "QYeBgO5qdjbFAn1Dm0K4uv6loFiizj7iY7739zN3scc/or7FHskzL630tLRJR+IfGyj8qDdmIbuHM22C", + "8VKq00VZnH3E/+DpClZkE36f6Q0/Q/+Us48NRLjPHUQ0f6+7hy0wT60HTsxmCt1odn0++2j/DSaCTQGS", + "mUeqzYXmfHEqpnCZjZ6PXgaNvl1AejvCcsroV42n/en5eaQaQtCLWOZDpzmgbuXZ+bMBHbjQYScXFdzt", + "+BO/5WLNCebOtjdRuVxSuUVpSpeSK/LmB8JmBNpTMOVnQO5HDaW8HxXlNGcpVpsK0PPhziHNZgc9w3LB", + "2xqX/uctT6M/dre5kQSx5+czf/fF+Fiz5cfGn80jpxalzsQ6mAW1RlZR2oXMfCxV+++zNWXaPOdcFj06", + "0yC7nTXQ/MxVamn9Wqc/73zBnO7Bj2EYVPTXM+pQPSqEipDtO7oOXowX2NgKS6D0NwKvj5ErLdpKyHa2", + "SaaMIwV9HFlxsiks2o9d3VLn1sWCm1tda+m7OWAwEYUUNEupwkr3dV7mWrLTsoS76LHD43S+Yy3uWgzW", + "sdNi0khAH1nRNzQjPn9HQl7T3GAFMnLhRJLG0uxhf/L5oLvkNrjBHG4rlfWxmxewgtxQDBGS7OM9X35O", + "DF9y8wShuWdoZvovPt/0VyBXLAVyDctCSCpZviU/8SrC42hW/B2St6TuDV6RvHXgk3TdDBqR8bQFzYJh", + "PsEFEL0hC8qz3AV6ixKLJxraRLucCDxDzBXm6+cVQiIANk0jZNZWribkqvIkQLu8DU/CcrJINqjwxsTI", + "dhKKXgbW0jTgKjFPbcMP5sATx5GSqci2rmLUSNK13tjg7Q7bw5dSH0/sSIGxr07Q6WnkXYv95/pRGj7y", + "Rs/fB8+79x/uPphvcoU+h+8/Bm+W52dnGKmyEEqfje7GH1vvmfDjhwpzvi7vqJBshYU+EGlCsjnjNE+c", + "0F8X2Rs9nZyP7v5PAAAA//827ZL/gQ8BAA==", } // 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 247084c256..91bacc4776 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 @@ -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,312 +837,319 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "AA==", } // 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 581a76dec3..8e06ae5a1c 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1175,10 +1175,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) @@ -1259,10 +1255,6 @@ func (v2 *Handlers) AccountAssetsInformation(ctx echo.Context, address basics.Ad // AccountApplicationsInformation returns application resources for a specific address. func (v2 *Handlers) AccountApplicationsInformation(ctx echo.Context, address basics.Address, params model.AccountApplicationsInformationParams) error { - if !v2.Node.Config().EnableExperimentalAPI { - return ctx.String(http.StatusNotFound, "/v2/accounts/{address}/applications was not enabled in the configuration file by setting the EnableExperimentalAPI to true") - } - var appGreaterThan uint64 = 0 if params.Next != nil { agt, err0 := strconv.ParseUint(*params.Next, 10, 64) 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 f46f017571..2e906f6e06 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -334,7 +334,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, diff --git a/ledger/ledger.go b/ledger/ledger.go index a0291f31cd..65ccf08db3 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -604,8 +604,7 @@ func (l *Ledger) LookupAssets(addr basics.Address, assetIDGT basics.AssetIndex, // 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) { - resources, lookupRound, err := l.accts.LookupApplicationResources(addr, appIDGT, limit, includeParams) - return resources, lookupRound, err + return l.accts.LookupApplicationResources(addr, appIDGT, limit, includeParams) } // lookupResource loads a resource that matches the request parameters from the accounts update From dde2190199088eae46bf42ee53e3ed6863692dea Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:51:18 -0500 Subject: [PATCH 03/13] Update ledger/acctupdates.go Co-authored-by: John Jannotti --- ledger/acctupdates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index f7e2a373c2..313228897d 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1295,7 +1295,7 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(assetIDGT), dbLimit, basics.AssetCreatable) if err != nil { - return nil, basics.Round(0), err + return nil, 0, err } if resourceDbRound == currentDBRound { From 4800d81c317349f2155a2bdc7afbf9d724d14108 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:51:29 -0500 Subject: [PATCH 04/13] Update ledger/acctupdates.go Co-authored-by: John Jannotti --- ledger/acctupdates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 313228897d..2d05006ab7 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1380,7 +1380,7 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba if resourceDbRound < currentDBRound { au.log.Errorf("accountUpdates.lookupAssetResources: database round %d is behind in-memory round %d", resourceDbRound, currentDBRound) - return nil, basics.Round(0), &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} + return nil, 0, &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} } au.accountsMu.RLock() needUnlock = true From f202ed2e5fefc6ad013d04498f9aeaa4cda4bb59 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:57:53 -0500 Subject: [PATCH 05/13] Exclude limit=0 case as invalid in lookupAssetResources and lookupApplicationResources of acctupdates.go. --- ledger/acctupdates.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 2d05006ab7..72859dc2be 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1221,6 +1221,10 @@ func (au *accountUpdates) lookupResource(rnd basics.Round, addr basics.Address, // 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) { + if limit == 0 { + return nil, basics.Round(0), nil + } + needUnlock := true au.accountsMu.RLock() defer func() { @@ -1288,10 +1292,7 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba // 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 - if limit > 0 { - dbLimit += uint64(numDeltaDeleted) - } + dbLimit := limit + uint64(numDeltaDeleted) persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(assetIDGT), dbLimit, basics.AssetCreatable) if err != nil { @@ -1371,7 +1372,7 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba slices.SortFunc(result, func(a, b ledgercore.AssetResourceWithIDs) int { return cmp.Compare(a.AssetID, b.AssetID) }) - if limit > 0 && uint64(len(result)) > limit { + if uint64(len(result)) > limit { result = result[:limit] } @@ -1394,6 +1395,10 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba // 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) { + if limit == 0 { + return nil, basics.Round(0), nil + } + needUnlock := true au.accountsMu.RLock() defer func() { @@ -1461,10 +1466,7 @@ func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDG // 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 - if limit > 0 { - dbLimit += uint64(numDeltaDeleted) - } + dbLimit := limit + uint64(numDeltaDeleted) persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(appIDGT), dbLimit, basics.AppCreatable) if err != nil { @@ -1550,7 +1552,7 @@ func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDG slices.SortFunc(result, func(a, b ledgercore.AppResourceWithIDs) int { return cmp.Compare(a.AppID, b.AppID) }) - if limit > 0 && uint64(len(result)) > limit { + if uint64(len(result)) > limit { result = result[:limit] } From 2996de4538d9810d19dae6590231e38ffd19bb0a Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:06:24 -0500 Subject: [PATCH 06/13] Based on CR feedback: fixed bug resulting in params not being returned when a holding was deleted. Removed intermediate assetDeltaEntry/appDeltaEntry types in favor of the pre-existing AssetResourceRecord/AppResourceRecord types. Cleaned up delta/db entry if/else merge logic for both assets and applications. --- ledger/acctdeltas_test.go | 88 ++++++++++------- ledger/acctupdates.go | 196 ++++++++++++++------------------------ 2 files changed, 126 insertions(+), 158 deletions(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 59bf286074..10de18836e 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -3707,22 +3707,23 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { knownCreatables := make(map[basics.CreatableIndex]bool) - // Round 1: create assets 1000-1004 with params and holdings + // Round 1: create assets 1000-1005 with params and holdings // 1000: will have holding modified, then overridden in a second delta round - // 1001: will be deleted in delta + // 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 { var updates ledgercore.AccountDeltas updates.Upsert(testAddr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{ MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, - TotalAssetParams: 5, - TotalAssets: 5, + TotalAssetParams: 6, + TotalAssets: 6, }, }) - for assetIdx := uint64(1000); assetIdx <= 1004; assetIdx++ { + for assetIdx := uint64(1000); assetIdx <= 1005; assetIdx++ { updates.UpsertAssetResource(testAddr, basics.AssetIndex(assetIdx), ledgercore.AssetParamsDelta{ Params: &basics.AssetParams{ @@ -3744,7 +3745,7 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { auNewBlock(t, 1, au, base, opts, nil) auCommitSync(t, 1, au, ml) - for assetIdx := uint64(1000); assetIdx <= 1004; assetIdx++ { + for assetIdx := uint64(1000); assetIdx <= 1005; assetIdx++ { knownCreatables[basics.CreatableIndex(assetIdx)] = true } } @@ -3765,13 +3766,17 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { deltaRound1 := basics.Round(conf.MaxAcctLookback + 3) { var updates ledgercore.AccountDeltas - // 1005: new creation (not in DB) + // 1005: delete both holding and params updates.UpsertAssetResource(testAddr, basics.AssetIndex(1005), + ledgercore.AssetParamsDelta{Deleted: true}, + ledgercore.AssetHoldingDelta{Deleted: true}) + // 1006: new creation (not in DB) + updates.UpsertAssetResource(testAddr, basics.AssetIndex(1006), ledgercore.AssetParamsDelta{ - Params: &basics.AssetParams{Total: 5000, UnitName: "A1005"}, + Params: &basics.AssetParams{Total: 6000, UnitName: "A1006"}, }, ledgercore.AssetHoldingDelta{ - Holding: &basics.AssetHolding{Amount: 5000}, + Holding: &basics.AssetHolding{Amount: 6000}, }) // 1001: delete holding updates.UpsertAssetResource(testAddr, basics.AssetIndex(1001), @@ -3818,8 +3823,9 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { require.NoError(t, err) require.Equal(t, deltaRound2, rnd) - // Expected: 1000, 1002, 1003, 1004, 1005. (1001 deleted) - require.Len(t, resources, 5) + // 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 { @@ -3831,8 +3837,11 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { require.NotNil(t, assetMap[basics.AssetIndex(1000)].AssetParams) require.Equal(t, uint64(1_000_000), assetMap[basics.AssetIndex(1000)].AssetParams.Total) - // 1001: deleted - require.NotContains(t, assetMap, basics.AssetIndex(1001)) + // 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) @@ -3850,10 +3859,13 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { require.Nil(t, assetMap[basics.AssetIndex(1004)].AssetParams) require.True(t, assetMap[basics.AssetIndex(1004)].Creator.IsZero()) - // 1005: new creation from delta - require.Equal(t, uint64(5000), assetMap[basics.AssetIndex(1005)].AssetHolding.Amount) - require.NotNil(t, assetMap[basics.AssetIndex(1005)].AssetParams) - require.Equal(t, uint64(5000), assetMap[basics.AssetIndex(1005)].AssetParams.Total) + // 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) } // TestLookupApplicationResourcesWithDeltas verifies that lookupApplicationResources properly @@ -3886,22 +3898,23 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { knownCreatables := make(map[basics.CreatableIndex]bool) - // Round 1: create apps 2000-2004 with params and local state + // 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 be deleted in delta + // 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: 5, - TotalAppLocalStates: 5, + TotalAppParams: 6, + TotalAppLocalStates: 6, }, }) - for appIdx := uint64(2000); appIdx <= 2004; appIdx++ { + for appIdx := uint64(2000); appIdx <= 2005; appIdx++ { updates.UpsertAppResource(testAddr, basics.AppIndex(appIdx), ledgercore.AppParamsDelta{ Params: &basics.AppParams{ @@ -3924,7 +3937,7 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { auNewBlock(t, 1, au, base, opts, nil) auCommitSync(t, 1, au, ml) - for appIdx := uint64(2000); appIdx <= 2004; appIdx++ { + for appIdx := uint64(2000); appIdx <= 2005; appIdx++ { knownCreatables[basics.CreatableIndex(appIdx)] = true } } @@ -3945,14 +3958,18 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { deltaRound1 := basics.Round(conf.MaxAcctLookback + 3) { var updates ledgercore.AccountDeltas - // 2005: new creation (not in DB) + // 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: 50}, + Schema: basics.StateSchema{NumUint: 60}, }, }) // 2001: delete local state @@ -4008,8 +4025,9 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { require.NoError(t, err) require.Equal(t, deltaRound2, rnd) - // Expected: 2000, 2002, 2003, 2004, 2005. (2001 deleted) - require.Len(t, resources, 5) + // 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 { @@ -4021,8 +4039,11 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { require.NotNil(t, appMap[basics.AppIndex(2000)].AppParams) require.Equal(t, []byte{0x06, 0x81, 0x01}, appMap[basics.AppIndex(2000)].AppParams.ApprovalProgram) - // 2001: deleted - require.NotContains(t, appMap, basics.AppIndex(2001)) + // 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) @@ -4039,9 +4060,12 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { require.Nil(t, appMap[basics.AppIndex(2004)].AppParams) require.True(t, appMap[basics.AppIndex(2004)].Creator.IsZero()) - // 2005: new creation from delta - require.Equal(t, uint64(50), appMap[basics.AppIndex(2005)].AppLocalState.Schema.NumUint) - require.NotNil(t, appMap[basics.AppIndex(2005)].AppParams) + // 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) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 72859dc2be..15e3e71f86 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1233,54 +1233,27 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba } }() - // A delta may modify only part of a resource (e.g. holding but not params), so we - // track exactly which fields were changed, then overlay these onto DB results. - type assetDeltaEntry struct { - holdingDeleted bool - holdingModified bool - holding *basics.AssetHolding - paramsDeleted bool - paramsModified bool - params *basics.AssetParams - } - 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]assetDeltaEntry) + deltaResults := make(map[basics.AssetIndex]ledgercore.AssetResourceRecord) numDeltaDeleted := 0 for i := currentDeltaLen; i > 0; { i-- - delta := &au.deltas[i] - for _, assetRes := range delta.Accts.AssetResources { - if assetRes.Addr != addr || assetRes.Aidx <= assetIDGT { + for _, rec := range au.deltas[i].Accts.AssetResources { + if rec.Addr != addr || rec.Aidx <= assetIDGT { continue } - if _, ok := deltaResults[assetRes.Aidx]; ok { + if _, ok := deltaResults[rec.Aidx]; ok { continue } - - entry := assetDeltaEntry{} - if assetRes.Holding.Deleted { - entry.holdingDeleted = true - entry.holdingModified = true + deltaResults[rec.Aidx] = rec + if rec.Holding.Deleted { numDeltaDeleted++ - } else if assetRes.Holding.Holding != nil { - entry.holdingModified = true - entry.holding = assetRes.Holding.Holding - } - if assetRes.Params.Deleted { - entry.paramsDeleted = true - entry.paramsModified = true - } else if assetRes.Params.Params != nil { - entry.paramsModified = true - entry.params = assetRes.Params.Params } - - deltaResults[assetRes.Aidx] = entry } } @@ -1316,57 +1289,56 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba assetID := basics.AssetIndex(pd.Aidx) seenInDB[assetID] = true - dEntry, inDelta := deltaResults[assetID] - if inDelta && dEntry.holdingDeleted { - continue - } + d, inDelta := deltaResults[assetID] - ah := pd.Data.GetAssetHolding() - arwi := ledgercore.AssetResourceWithIDs{ - AssetID: assetID, - AssetResource: ledgercore.AssetResource{ - AssetHolding: &ah, - }, - } + arwi := ledgercore.AssetResourceWithIDs{AssetID: assetID} - if inDelta && dEntry.holdingModified { - arwi.AssetHolding = dEntry.holding + 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 !pd.Creator.IsZero() && !(inDelta && dEntry.paramsDeleted) { + 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 inDelta && dEntry.paramsModified && !dEntry.paramsDeleted { - arwi.Creator = addr - arwi.AssetParams = dEntry.params - } - result = append(result, arwi) + 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, dEntry := range deltaResults { - if seenInDB[assetID] || dEntry.holdingDeleted || !dEntry.holdingModified { + for assetID, d := range deltaResults { + if seenInDB[assetID] { continue } if dbHasMore && assetID > dbMaxID { continue } - arwi := ledgercore.AssetResourceWithIDs{ - AssetID: assetID, - AssetResource: ledgercore.AssetResource{ - AssetHolding: dEntry.holding, - }, + arwi := ledgercore.AssetResourceWithIDs{AssetID: assetID} + if !d.Holding.Deleted && d.Holding.Holding != nil { + arwi.AssetHolding = d.Holding.Holding } - if dEntry.params != nil { + if !d.Params.Deleted && d.Params.Params != nil { arwi.Creator = addr - arwi.AssetParams = dEntry.params + arwi.AssetParams = d.Params.Params + } + if arwi.AssetHolding != nil || arwi.AssetParams != nil { + result = append(result, arwi) } - result = append(result, arwi) } slices.SortFunc(result, func(a, b ledgercore.AssetResourceWithIDs) int { @@ -1407,54 +1379,27 @@ func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDG } }() - // A delta may modify only part of a resource (e.g. local state but not params), - // so we track exactly which fields changed, then overlay these onto DB results. - type appDeltaEntry struct { - stateDeleted bool - stateModified bool - localState *basics.AppLocalState - paramsDeleted bool - paramsModified bool - params *basics.AppParams - } - 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]appDeltaEntry) + deltaResults := make(map[basics.AppIndex]ledgercore.AppResourceRecord) numDeltaDeleted := 0 for i := currentDeltaLen; i > 0; { i-- - delta := &au.deltas[i] - for _, appRes := range delta.Accts.AppResources { - if appRes.Addr != addr || appRes.Aidx <= appIDGT { + for _, rec := range au.deltas[i].Accts.AppResources { + if rec.Addr != addr || rec.Aidx <= appIDGT { continue } - if _, ok := deltaResults[appRes.Aidx]; ok { + if _, ok := deltaResults[rec.Aidx]; ok { continue } - - entry := appDeltaEntry{} - if appRes.State.Deleted { - entry.stateDeleted = true - entry.stateModified = true + deltaResults[rec.Aidx] = rec + if rec.State.Deleted { numDeltaDeleted++ - } else if appRes.State.LocalState != nil { - entry.stateModified = true - entry.localState = appRes.State.LocalState - } - if appRes.Params.Deleted { - entry.paramsDeleted = true - entry.paramsModified = true - } else if appRes.Params.Params != nil { - entry.paramsModified = true - entry.params = appRes.Params.Params } - - deltaResults[appRes.Aidx] = entry } } @@ -1490,63 +1435,62 @@ func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDG appID := basics.AppIndex(pd.Aidx) seenInDB[appID] = true - dEntry, inDelta := deltaResults[appID] - if inDelta && dEntry.stateDeleted { - continue - } + d, inDelta := deltaResults[appID] - als := pd.Data.GetAppLocalState() - arwi := ledgercore.AppResourceWithIDs{ - AppID: appID, - AppResource: ledgercore.AppResource{ - AppLocalState: &als, - }, - } + arwi := ledgercore.AppResourceWithIDs{AppID: appID} - if inDelta && dEntry.stateModified { - arwi.AppLocalState = dEntry.localState + 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 !pd.Creator.IsZero() && !(inDelta && dEntry.paramsDeleted) { + 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 { - ap := pd.Data.GetAppParams() - arwi.AppResource.AppParams = &ap + arwi.AppResource.AppParams = d.Params.Params } - } - if inDelta && dEntry.paramsModified && !dEntry.paramsDeleted { - arwi.Creator = addr + } else if !pd.Creator.IsZero() { + arwi.Creator = pd.Creator if includeParams { - arwi.AppResource.AppParams = dEntry.params + ap := pd.Data.GetAppParams() + arwi.AppResource.AppParams = &ap } } - result = append(result, arwi) + 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, dEntry := range deltaResults { - if seenInDB[appID] || dEntry.stateDeleted || !dEntry.stateModified { + for appID, d := range deltaResults { + if seenInDB[appID] { continue } if dbHasMore && appID > dbMaxID { continue } - arwi := ledgercore.AppResourceWithIDs{ - AppID: appID, - AppResource: ledgercore.AppResource{ - AppLocalState: dEntry.localState, - }, + arwi := ledgercore.AppResourceWithIDs{AppID: appID} + if !d.State.Deleted && d.State.LocalState != nil { + arwi.AppLocalState = d.State.LocalState } - if dEntry.params != nil { + if !d.Params.Deleted && d.Params.Params != nil { arwi.Creator = addr if includeParams { - arwi.AppResource.AppParams = dEntry.params + arwi.AppResource.AppParams = d.Params.Params } } - result = append(result, arwi) + if arwi.AppLocalState != nil || arwi.AppParams != nil { + result = append(result, arwi) + } } slices.SortFunc(result, func(a, b ledgercore.AppResourceWithIDs) int { From 3c0d360848809b0cffaa86aa7af886a073addc76 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:36:59 -0500 Subject: [PATCH 07/13] Update ledger/acctupdates.go Co-authored-by: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> --- ledger/acctupdates.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 15e3e71f86..918f12b789 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1241,8 +1241,7 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba deltaResults := make(map[basics.AssetIndex]ledgercore.AssetResourceRecord) numDeltaDeleted := 0 - for i := currentDeltaLen; i > 0; { - i-- + for i := currentDeltaLen-1; i > 0; i-- { for _, rec := range au.deltas[i].Accts.AssetResources { if rec.Addr != addr || rec.Aidx <= assetIDGT { continue From ee82b36d894482daccebb9539b2595e8a0b1b86b Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:28:49 -0500 Subject: [PATCH 08/13] Fix formatting issue. --- ledger/acctupdates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 918f12b789..aea28ef826 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1241,7 +1241,7 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba deltaResults := make(map[basics.AssetIndex]ledgercore.AssetResourceRecord) numDeltaDeleted := 0 - for i := currentDeltaLen-1; i > 0; i-- { + for i := currentDeltaLen - 1; i > 0; i-- { for _, rec := range au.deltas[i].Accts.AssetResources { if rec.Addr != addr || rec.Aidx <= assetIDGT { continue From c06d9645984b391d2a07ae3a479c867a41e7c34f Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:22:17 -0500 Subject: [PATCH 09/13] Cover our handling of deletions when another address is opted into the holding with a zero balance. --- ledger/acctdeltas_test.go | 68 +++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 10de18836e..47b8961b70 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -3691,11 +3691,15 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { accts := setupAccts(5) - var testAddr basics.Address + var testAddr, addr2 basics.Address for addr := range accts[0] { if addr != testSinkAddr && addr != testPoolAddr { - testAddr = addr - break + if testAddr.IsZero() { + testAddr = addr + } else { + addr2 = addr + break + } } } @@ -3714,13 +3718,14 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { // 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; addr2 opts in with zero balance { var updates ledgercore.AccountDeltas updates.Upsert(testAddr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{ MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, - TotalAssetParams: 6, - TotalAssets: 6, + TotalAssetParams: 7, + TotalAssets: 7, }, }) for assetIdx := uint64(1000); assetIdx <= 1005; assetIdx++ { @@ -3737,6 +3742,29 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { }) } + // 1007: testAddr is creator; addr2 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(testAddr, basics.AssetIndex(1007), + ledgercore.AssetParamsDelta{ + Params: &basics.AssetParams{ + Total: 7000, + UnitName: "A1007", + AssetName: "Asset1007", + }, + }, + ledgercore.AssetHoldingDelta{ + Holding: &basics.AssetHolding{Amount: 7000}, + }) + updates.Upsert(addr2, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAssets: 1, + }, + }) + updates.UpsertAssetResource(addr2, basics.AssetIndex(1007), + ledgercore.AssetParamsDelta{}, + ledgercore.AssetHoldingDelta{Holding: &basics.AssetHolding{Amount: 0}}) + base := accts[0] newAccts := applyPartialDeltas(base, updates) accts = append(accts, newAccts) @@ -3748,11 +3776,25 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { for assetIdx := uint64(1000); assetIdx <= 1005; assetIdx++ { knownCreatables[basics.CreatableIndex(assetIdx)] = true } + knownCreatables[basics.CreatableIndex(1007)] = true } - // Add empty rounds so round 1 data flushes past MaxAcctLookback into DB + // Add empty rounds so round 1 data flushes past MaxAcctLookback into DB. + // Round 2 destroys asset 1007, committing its deletion; addr2's zero holding survives. for i := basics.Round(2); i <= basics.Round(conf.MaxAcctLookback+2); i++ { var updates ledgercore.AccountDeltas + if i == 2 { + updates.Upsert(testAddr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{ + MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, + TotalAssetParams: 6, + TotalAssets: 6, + }, + }) + updates.UpsertAssetResource(testAddr, basics.AssetIndex(1007), + ledgercore.AssetParamsDelta{Deleted: true}, + ledgercore.AssetHoldingDelta{Deleted: true}) + } base := accts[i-1] newAccts := applyPartialDeltas(base, updates) accts = append(accts, newAccts) @@ -3866,6 +3908,20 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { 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) + + // addr2 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 addr2'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. + addr2Resources, addr2Rnd, err := au.LookupAssetResources(addr2, 0, 100) + require.NoError(t, err) + require.Equal(t, deltaRound2, addr2Rnd) + require.Len(t, addr2Resources, 1) + require.Equal(t, basics.AssetIndex(1007), addr2Resources[0].AssetID) + require.NotNil(t, addr2Resources[0].AssetHolding) + require.Equal(t, uint64(0), addr2Resources[0].AssetHolding.Amount) + require.Nil(t, addr2Resources[0].AssetParams) + require.True(t, addr2Resources[0].Creator.IsZero()) } // TestLookupApplicationResourcesWithDeltas verifies that lookupApplicationResources properly From a32e0da21cbdedd6d421b6e4e826cb08ce988c7f Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:35:24 -0500 Subject: [PATCH 10/13] Update ledger/acctdeltas_test.go Co-authored-by: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> --- ledger/acctdeltas_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 47b8961b70..6cbdda6895 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -3919,7 +3919,7 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { require.Len(t, addr2Resources, 1) require.Equal(t, basics.AssetIndex(1007), addr2Resources[0].AssetID) require.NotNil(t, addr2Resources[0].AssetHolding) - require.Equal(t, uint64(0), addr2Resources[0].AssetHolding.Amount) + require.Equal(t, basics.AssetHolding{}, addr2Resources[0].AssetHolding) require.Nil(t, addr2Resources[0].AssetParams) require.True(t, addr2Resources[0].Creator.IsZero()) } From 75d4eaaedac0f830ac9788fbe6f97e3c4f6018a7 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:43:45 -0500 Subject: [PATCH 11/13] CR naming fixes. --- ledger/acctdeltas_test.go | 64 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index 6cbdda6895..b43bcd8832 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -3691,13 +3691,13 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { accts := setupAccts(5) - var testAddr, addr2 basics.Address + var creatorAddr, optinAddr basics.Address for addr := range accts[0] { if addr != testSinkAddr && addr != testPoolAddr { - if testAddr.IsZero() { - testAddr = addr + if creatorAddr.IsZero() { + creatorAddr = addr } else { - addr2 = addr + optinAddr = addr break } } @@ -3718,10 +3718,10 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { // 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; addr2 opts in with zero balance + // 1007: will be fully deleted in a committed round; optinAddr opts in with zero balance { var updates ledgercore.AccountDeltas - updates.Upsert(testAddr, ledgercore.AccountData{ + updates.Upsert(creatorAddr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{ MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, TotalAssetParams: 7, @@ -3729,7 +3729,7 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { }, }) for assetIdx := uint64(1000); assetIdx <= 1005; assetIdx++ { - updates.UpsertAssetResource(testAddr, basics.AssetIndex(assetIdx), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(assetIdx), ledgercore.AssetParamsDelta{ Params: &basics.AssetParams{ Total: assetIdx * 1000, @@ -3742,9 +3742,9 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { }) } - // 1007: testAddr is creator; addr2 opts in with zero balance and asset will be deleted + // 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(testAddr, basics.AssetIndex(1007), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1007), ledgercore.AssetParamsDelta{ Params: &basics.AssetParams{ Total: 7000, @@ -3755,13 +3755,13 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { ledgercore.AssetHoldingDelta{ Holding: &basics.AssetHolding{Amount: 7000}, }) - updates.Upsert(addr2, ledgercore.AccountData{ + updates.Upsert(optinAddr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{ MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, TotalAssets: 1, }, }) - updates.UpsertAssetResource(addr2, basics.AssetIndex(1007), + updates.UpsertAssetResource(optinAddr, basics.AssetIndex(1007), ledgercore.AssetParamsDelta{}, ledgercore.AssetHoldingDelta{Holding: &basics.AssetHolding{Amount: 0}}) @@ -3780,18 +3780,18 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { } // Add empty rounds so round 1 data flushes past MaxAcctLookback into DB. - // Round 2 destroys asset 1007, committing its deletion; addr2's zero holding survives. + // Round 2 destroys asset 1007, committing its deletion; optinAddr's zero holding survives. for i := basics.Round(2); i <= basics.Round(conf.MaxAcctLookback+2); i++ { var updates ledgercore.AccountDeltas if i == 2 { - updates.Upsert(testAddr, ledgercore.AccountData{ + updates.Upsert(creatorAddr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{ MicroAlgos: basics.MicroAlgos{Raw: 1_000_000}, TotalAssetParams: 6, TotalAssets: 6, }, }) - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1007), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1007), ledgercore.AssetParamsDelta{Deleted: true}, ledgercore.AssetHoldingDelta{Deleted: true}) } @@ -3809,11 +3809,11 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { { var updates ledgercore.AccountDeltas // 1005: delete both holding and params - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1005), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1005), ledgercore.AssetParamsDelta{Deleted: true}, ledgercore.AssetHoldingDelta{Deleted: true}) // 1006: new creation (not in DB) - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1006), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1006), ledgercore.AssetParamsDelta{ Params: &basics.AssetParams{Total: 6000, UnitName: "A1006"}, }, @@ -3821,23 +3821,23 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { Holding: &basics.AssetHolding{Amount: 6000}, }) // 1001: delete holding - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1001), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1001), ledgercore.AssetParamsDelta{}, ledgercore.AssetHoldingDelta{Deleted: true}) // 1000: modify holding (will be overridden by delta round 2) - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1000), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1000), ledgercore.AssetParamsDelta{}, ledgercore.AssetHoldingDelta{ Holding: &basics.AssetHolding{Amount: 9999}, }) // 1003: modify params only (holding unchanged) - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1003), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1003), ledgercore.AssetParamsDelta{ Params: &basics.AssetParams{Total: 7777, UnitName: "A1003new"}, }, ledgercore.AssetHoldingDelta{}) // 1004: delete params (holding remains) - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1004), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1004), ledgercore.AssetParamsDelta{Deleted: true}, ledgercore.AssetHoldingDelta{}) @@ -3850,7 +3850,7 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { deltaRound2 := deltaRound1 + 1 { var updates ledgercore.AccountDeltas - updates.UpsertAssetResource(testAddr, basics.AssetIndex(1000), + updates.UpsertAssetResource(creatorAddr, basics.AssetIndex(1000), ledgercore.AssetParamsDelta{}, ledgercore.AssetHoldingDelta{ Holding: &basics.AssetHolding{Amount: 5555}, @@ -3861,7 +3861,7 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { auNewBlock(t, deltaRound2, au, base, opts, nil) } - resources, rnd, err := au.LookupAssetResources(testAddr, 0, 100) + resources, rnd, err := au.LookupAssetResources(creatorAddr, 0, 100) require.NoError(t, err) require.Equal(t, deltaRound2, rnd) @@ -3909,19 +3909,19 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { require.NotNil(t, assetMap[basics.AssetIndex(1006)].AssetParams) require.Equal(t, uint64(6000), assetMap[basics.AssetIndex(1006)].AssetParams.Total) - // addr2 opted in to asset 1007 with a zero balance before it was destroyed. The protocol + // 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 addr2's holding persists in the DB even after the asset params are gone. + // 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. - addr2Resources, addr2Rnd, err := au.LookupAssetResources(addr2, 0, 100) + optinAddrResources, optinAddrRnd, err := au.LookupAssetResources(optinAddr, 0, 100) require.NoError(t, err) - require.Equal(t, deltaRound2, addr2Rnd) - require.Len(t, addr2Resources, 1) - require.Equal(t, basics.AssetIndex(1007), addr2Resources[0].AssetID) - require.NotNil(t, addr2Resources[0].AssetHolding) - require.Equal(t, basics.AssetHolding{}, addr2Resources[0].AssetHolding) - require.Nil(t, addr2Resources[0].AssetParams) - require.True(t, addr2Resources[0].Creator.IsZero()) + 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 From 2ffe188a3a959a095f24d88125f1559211e164d1 Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:32:39 -0500 Subject: [PATCH 12/13] CR bug fixes: Fixed an off-by-one bug in lookupAssetResources that skipped deltas[0], added missing trackerMu.RLock() guards to LookupAssets and LookupApplications, cleaned up an error return for consistency, and adjusted the tests to use MaxAcctLookback = 0 so deltas[0] contains meaningful data that actually exercises the off-by-one fix. --- ledger/acctdeltas_test.go | 14 +++++++++++--- ledger/acctupdates.go | 5 +++-- ledger/ledger.go | 6 ++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go index b43bcd8832..6efec70595 100644 --- a/ledger/acctdeltas_test.go +++ b/ledger/acctdeltas_test.go @@ -3707,6 +3707,10 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { 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) @@ -3779,8 +3783,8 @@ func TestLookupAssetResourcesWithDeltas(t *testing.T) { knownCreatables[basics.CreatableIndex(1007)] = true } - // Add empty rounds so round 1 data flushes past MaxAcctLookback into DB. - // Round 2 destroys asset 1007, committing its deletion; optinAddr's zero holding survives. + // 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 { @@ -3950,6 +3954,10 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { 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) @@ -3998,7 +4006,7 @@ func TestLookupApplicationResourcesWithDeltas(t *testing.T) { } } - // Add empty rounds so round 1 data flushes past MaxAcctLookback into DB + // 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] diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index aea28ef826..142c24d871 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1241,7 +1241,8 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba deltaResults := make(map[basics.AssetIndex]ledgercore.AssetResourceRecord) numDeltaDeleted := 0 - for i := currentDeltaLen - 1; i > 0; i-- { + for i := currentDeltaLen; i > 0; { + i-- for _, rec := range au.deltas[i].Accts.AssetResources { if rec.Addr != addr || rec.Aidx <= assetIDGT { continue @@ -1414,7 +1415,7 @@ func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDG persistedResources, resourceDbRound, err := au.accountsq.LookupLimitedResources(addr, basics.CreatableIndex(appIDGT), dbLimit, basics.AppCreatable) if err != nil { - return nil, basics.Round(0), err + return nil, 0, err } if resourceDbRound == currentDBRound { diff --git a/ledger/ledger.go b/ledger/ledger.go index 65ccf08db3..9cf818c7e6 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -597,6 +597,9 @@ 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 } @@ -604,6 +607,9 @@ func (l *Ledger) LookupAssets(addr basics.Address, assetIDGT basics.AssetIndex, // 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) } From 39ba85400e08c85303c70ad4ad9049e3bb7d928c Mon Sep 17 00:00:00 2001 From: Gary Malouf <982483+gmalouf@users.noreply.github.com> Date: Wed, 4 Mar 2026 10:21:03 -0500 Subject: [PATCH 13/13] Added note to AGENTS.md around keeping the application/asset/box resource lookup functions aligned (and candidates for eventual consolidation). Two tweaks based on CR feedback made in ledger/acctupdates.go. --- AGENTS.md | 5 +++++ ledger/acctupdates.go | 18 +++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) 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/ledger/acctupdates.go b/ledger/acctupdates.go index 142c24d871..aedf786e15 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -1221,10 +1221,6 @@ func (au *accountUpdates) lookupResource(rnd basics.Round, addr basics.Address, // 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) { - if limit == 0 { - return nil, basics.Round(0), nil - } - needUnlock := true au.accountsMu.RLock() defer func() { @@ -1233,6 +1229,10 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba } }() + if limit == 0 { + return nil, au.cachedDBRound + basics.Round(len(au.deltas)), nil + } + for { currentDBRound := au.cachedDBRound currentDeltaLen := len(au.deltas) @@ -1367,10 +1367,6 @@ func (au *accountUpdates) lookupAssetResources(addr basics.Address, assetIDGT ba // 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) { - if limit == 0 { - return nil, basics.Round(0), nil - } - needUnlock := true au.accountsMu.RLock() defer func() { @@ -1379,6 +1375,10 @@ func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDG } }() + if limit == 0 { + return nil, au.cachedDBRound + basics.Round(len(au.deltas)), nil + } + for { currentDBRound := au.cachedDBRound currentDeltaLen := len(au.deltas) @@ -1505,7 +1505,7 @@ func (au *accountUpdates) lookupApplicationResources(addr basics.Address, appIDG if resourceDbRound < currentDBRound { au.log.Errorf("accountUpdates.lookupApplicationResources: database round %d is behind in-memory round %d", resourceDbRound, currentDBRound) - return nil, basics.Round(0), &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} + return nil, 0, &StaleDatabaseRoundError{databaseRound: resourceDbRound, memoryRound: currentDBRound} } au.accountsMu.RLock() needUnlock = true