diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 90b90cec8b..064f100f4a 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -9,9 +9,6 @@ on: - 'tools/block-generator/**' - 'tools/x-repo-types/**' pull_request: - paths: - - 'tools/block-generator/**' - - 'tools/x-repo-types/**' jobs: tools_test: diff --git a/cmd/algons/dnsaddrCmd.go b/cmd/algons/dnsaddrCmd.go index c7ab7d9546..82dcca59e0 100644 --- a/cmd/algons/dnsaddrCmd.go +++ b/cmd/algons/dnsaddrCmd.go @@ -78,7 +78,6 @@ var dnsaddrTreeCmd = &cobra.Command{ } }, } - var dnsaddrTreeDeleteCmd = &cobra.Command{ Use: "delete", Short: "Recursively resolves and deletes the dnsaddr entries of the given domain", @@ -97,8 +96,8 @@ var dnsaddrTreeDeleteCmd = &cobra.Command{ } cloudflareDNS := cloudflare.NewDNS(cfZoneID, cfToken) var recordsToDelete []cloudflare.DNSRecordResponseEntry - err = dnsaddr.Iterate(addr, controller, func(dnsaddr multiaddr.Multiaddr, entries []multiaddr.Multiaddr) error { - domain, _ := dnsaddr.ValueForProtocol(multiaddr.P_DNSADDR) + err = dnsaddr.Iterate(addr, controller, func(entryFrom multiaddr.Multiaddr, entries []multiaddr.Multiaddr) error { + domain, _ := entryFrom.ValueForProtocol(multiaddr.P_DNSADDR) name := fmt.Sprintf("_dnsaddr.%s", domain) fmt.Printf("listing records for %s\n", name) records, err := cloudflareDNS.ListDNSRecord(context.Background(), "TXT", name, "", "", "", "") diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go index 03cf088f42..64dfe7329d 100644 --- a/cmd/tealdbg/localLedger.go +++ b/cmd/tealdbg/localLedger.go @@ -281,10 +281,6 @@ func (l *localLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) { return bookkeeping.BlockHeader{}, nil } -func (l *localLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) { - return bookkeeping.BlockHeader{}, nil -} - func (l *localLedger) GetStateProofVerificationContext(_ basics.Round) (*ledgercore.StateProofVerificationContext, error) { return nil, fmt.Errorf("localLedger: GetStateProofVerificationContext, needed for state proof verification, is not implemented in debugger") } diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 5adf017114..02e2349de3 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1111,7 +1111,7 @@ "public", "nonparticipating" ], - "description": "Waits for a block to appear after round {round} and returns the node's status at the time.", + "description": "Waits for a block to appear after round {round} and returns the node's status at the time. There is a 1 minute timeout, when reached the current status is returned regardless of whether or not it is the round after the given round.", "produces": [ "application/json" ], @@ -1132,10 +1132,11 @@ ], "responses": { "200": { + "description": "The round after the given round, or the current round if a timeout occurs.", "$ref": "#/responses/NodeStatusResponse" }, "400": { - "description": "Bad Request -- number must be non-negative integer ", + "description": "Bad Request -- number must be non-negative integer", "schema": { "$ref": "#/definitions/ErrorResponse" } diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index f29797fe4f..5b784af1ac 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -5499,7 +5499,7 @@ }, "/v2/status/wait-for-block-after/{round}": { "get": { - "description": "Waits for a block to appear after round {round} and returns the node's status at the time.", + "description": "Waits for a block to appear after round {round} and returns the node's status at the time. There is a 1 minute timeout, when reached the current status is returned regardless of whether or not it is the round after the given round.", "operationId": "WaitForBlock", "parameters": [ { @@ -5648,7 +5648,7 @@ } } }, - "description": "Bad Request -- number must be non-negative integer " + "description": "Bad Request -- number must be non-negative integer" }, "401": { "content": { diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 5b9309e449..ef0de80850 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -241,10 +241,6 @@ func (dl *dryrunLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) return bookkeeping.BlockHeader{}, nil } -func (dl *dryrunLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) { - return bookkeeping.BlockHeader{}, nil -} - func (dl *dryrunLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { return nil } 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 774d725580..5ad094a2aa 100644 --- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go +++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go @@ -933,32 +933,33 @@ var swaggerSpec = []string{ "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", - "Fs2Hdx/+/wAAAP//3P3na2WCAQA=", + "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", + "RfPh3Yf/PwAA///0dvnw8YIBAA==", } // 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 fb904f890b..cd9fbbbd4a 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -66,6 +66,9 @@ const MaxTealSourceBytes = 200_000 // become quite large, so we allow up to 1MB const MaxTealDryrunBytes = 1_000_000 +// WaitForBlockTimeout is the timeout for the WaitForBlock endpoint. +var WaitForBlockTimeout = 1 * time.Minute + // Handlers is an implementation to the V2 route handler interface defined by the generated code. type Handlers struct { Node NodeInterface @@ -863,7 +866,7 @@ func (v2 *Handlers) WaitForBlock(ctx echo.Context, round uint64) error { select { case <-v2.Shutdown: return internalError(ctx, err, errServiceShuttingDown, v2.Log) - case <-time.After(1 * time.Minute): + case <-time.After(WaitForBlockTimeout): case <-ledger.Wait(basics.Round(round + 1)): } diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index a56577d549..de8a011458 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -72,16 +72,19 @@ import ( const stateProofInterval = uint64(256) func setupMockNodeForMethodGet(t *testing.T, status node.StatusReport, devmode bool) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { + return setupMockNodeForMethodGetWithShutdown(t, status, devmode, make(chan struct{})) +} + +func setupMockNodeForMethodGetWithShutdown(t *testing.T, status node.StatusReport, devmode bool, shutdown chan struct{}) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { numAccounts := 1 numTransactions := 1 offlineAccounts := true mockLedger, rootkeys, _, stxns, releasefunc := testingenv(t, numAccounts, numTransactions, offlineAccounts) mockNode := makeMockNode(mockLedger, t.Name(), nil, status, devmode) - dummyShutdownChan := make(chan struct{}) handler := v2.Handlers{ Node: mockNode, Log: logging.Base(), - Shutdown: dummyShutdownChan, + Shutdown: shutdown, } e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) @@ -585,9 +588,73 @@ func TestGetStatusAfterBlock(t *testing.T) { defer releasefunc() err := handler.WaitForBlock(c, 0) require.NoError(t, err) - // Expect 400 - the test ledger will always cause "errRequestedRoundInUnsupportedRound", - // as it has not participated in agreement to build blockheaders + require.Equal(t, 400, rec.Code) + msg, err := io.ReadAll(rec.Body) + require.NoError(t, err) + require.Contains(t, string(msg), "requested round would reach only after the protocol upgrade which isn't supported") +} + +func TestGetStatusAfterBlockShutdown(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + catchup := cannedStatusReportGolden + catchup.StoppedAtUnsupportedRound = false + shutdownChan := make(chan struct{}) + handler, c, rec, _, _, releasefunc := setupMockNodeForMethodGetWithShutdown(t, catchup, false, shutdownChan) + defer releasefunc() + + close(shutdownChan) + err := handler.WaitForBlock(c, 0) + require.NoError(t, err) + + require.Equal(t, 500, rec.Code) + msg, err := io.ReadAll(rec.Body) + require.NoError(t, err) + require.Contains(t, string(msg), "operation aborted as server is shutting down") +} + +func TestGetStatusAfterBlockDuringCatchup(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + catchup := cannedStatusReportGolden + catchup.StoppedAtUnsupportedRound = false + catchup.Catchpoint = "catchpoint" + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, catchup) + defer releasefunc() + + err := handler.WaitForBlock(c, 0) + require.NoError(t, err) + + require.Equal(t, 503, rec.Code) + msg, err := io.ReadAll(rec.Body) + require.NoError(t, err) + require.Contains(t, string(msg), "operation not available during catchup") +} + +func TestGetStatusAfterBlockTimeout(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + supported := cannedStatusReportGolden + supported.StoppedAtUnsupportedRound = false + handler, c, rec, _, _, releasefunc := setupTestForMethodGet(t, supported) + defer releasefunc() + + before := v2.WaitForBlockTimeout + defer func() { v2.WaitForBlockTimeout = before }() + v2.WaitForBlockTimeout = 1 * time.Millisecond + err := handler.WaitForBlock(c, 1000) + require.NoError(t, err) + + require.Equal(t, 200, rec.Code) + dec := json.NewDecoder(rec.Body) + var resp model.NodeStatusResponse + err = dec.Decode(&resp) + require.NoError(t, err) + require.Equal(t, uint64(1), resp.LastRound) } func TestGetTransactionParams(t *testing.T) { diff --git a/data/ledger_test.go b/data/ledger_test.go index cd7d6297a0..452bdba158 100644 --- a/data/ledger_test.go +++ b/data/ledger_test.go @@ -366,9 +366,11 @@ func TestConsensusVersion(t *testing.T) { require.NotNil(t, &l) blk := genesisInitState.Block + flushOffset := uint64(129) // pendingDeltasFlushThreshold = 128 will flush every 128 rounds (RewardsPool acct) + // txTailRetainSize = MaxTxnLife + DeeperBlockHeaderHistory = 1000 + 1 - // add 5 blocks. - for rnd := basics.Round(1); rnd < basics.Round(consensusParams.MaxTxnLife+5); rnd++ { + // add some blocks. + for rnd := basics.Round(1); rnd < basics.Round(consensusParams.MaxTxnLife+flushOffset); rnd++ { blk.BlockHeader.Round++ blk.BlockHeader.Seed[0] = byte(uint64(rnd)) blk.BlockHeader.Seed[1] = byte(uint64(rnd) / 256) @@ -378,31 +380,38 @@ func TestConsensusVersion(t *testing.T) { require.NoError(t, l.AddBlock(blk, agreement.Certificate{})) l.WaitForCommit(rnd) } - // ensure that all the first 5 has the expected version. - for rnd := basics.Round(consensusParams.MaxTxnLife); rnd < basics.Round(consensusParams.MaxTxnLife+5); rnd++ { + // ensure that all the first flushOffset have the expected version. + for rnd := basics.Round(consensusParams.MaxTxnLife); rnd < basics.Round(consensusParams.MaxTxnLife+flushOffset); rnd++ { ver, err := l.ConsensusVersion(rnd) require.NoError(t, err) require.Equal(t, previousProtocol, ver) } // the next UpgradeVoteRounds can also be known to have the previous version. - for rnd := basics.Round(consensusParams.MaxTxnLife + 5); rnd < basics.Round(consensusParams.MaxTxnLife+5+consensusParams.UpgradeVoteRounds); rnd++ { + for rnd := basics.Round(consensusParams.MaxTxnLife + flushOffset); rnd < basics.Round(consensusParams.MaxTxnLife+ + flushOffset+consensusParams.UpgradeVoteRounds); rnd++ { ver, err := l.ConsensusVersion(rnd) require.NoError(t, err) require.Equal(t, previousProtocol, ver) } // but two rounds ahead is not known. - ver, err := l.ConsensusVersion(basics.Round(consensusParams.MaxTxnLife + 6 + consensusParams.UpgradeVoteRounds)) + ver, err := l.ConsensusVersion(basics.Round(consensusParams.MaxTxnLife + flushOffset + 1 + consensusParams.UpgradeVoteRounds)) require.Equal(t, protocol.ConsensusVersion(""), ver) - require.Equal(t, ledgercore.ErrNoEntry{Round: basics.Round(consensusParams.MaxTxnLife + 6 + consensusParams.UpgradeVoteRounds), Latest: basics.Round(consensusParams.MaxTxnLife + 4), Committed: basics.Round(consensusParams.MaxTxnLife + 4)}, err) + require.Equal(t, ledgercore.ErrNoEntry{ + Round: basics.Round(consensusParams.MaxTxnLife + flushOffset + 1 + consensusParams.UpgradeVoteRounds), + Latest: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1), + Committed: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1)}, err) // check round #1 which was already dropped. ver, err = l.ConsensusVersion(basics.Round(1)) require.Equal(t, protocol.ConsensusVersion(""), ver) - require.Equal(t, ledgercore.ErrNoEntry{Round: basics.Round(1), Latest: basics.Round(consensusParams.MaxTxnLife + 4), Committed: basics.Round(consensusParams.MaxTxnLife + 4)}, err) + require.Equal(t, ledgercore.ErrNoEntry{ + Round: basics.Round(1), + Latest: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1), + Committed: basics.Round(consensusParams.MaxTxnLife + flushOffset - 1)}, err) // add another round, with upgrade - rnd := basics.Round(consensusParams.MaxTxnLife + 5) + rnd := basics.Round(consensusParams.MaxTxnLife + flushOffset) blk.BlockHeader.Round++ blk.BlockHeader.Seed[0] = byte(uint64(rnd)) blk.BlockHeader.Seed[1] = byte(uint64(rnd) / 256) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 2044301702..f1d6983836 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -205,7 +205,7 @@ func computeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 { // only exposes things that consensus has already agreed upon, so it is // "stateless" for signature purposes. type LedgerForSignature interface { - BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) + BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) } // NoHeaderLedger is intended for debugging situations in which it is reasonable @@ -213,8 +213,8 @@ type LedgerForSignature interface { type NoHeaderLedger struct { } -// BlockHdrCached always errors -func (NoHeaderLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) { +// BlockHdr always errors +func (NoHeaderLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) { return bookkeeping.BlockHeader{}, fmt.Errorf("no block header access") } @@ -224,7 +224,6 @@ type LedgerForLogic interface { Authorizer(addr basics.Address) (basics.Address, error) Round() basics.Round PrevTimestamp() int64 - BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error) AssetParams(aidx basics.AssetIndex) (basics.AssetParams, basics.Address, error) @@ -2949,7 +2948,7 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t if err != nil { return sv, err } - hdr, err := cx.SigLedger.BlockHdrCached(rnd) + hdr, err := cx.SigLedger.BlockHdr(rnd) if err != nil { return sv, err } @@ -5827,7 +5826,7 @@ func opBlock(cx *EvalContext) error { return fmt.Errorf("invalid block field %s", f) } - hdr, err := cx.SigLedger.BlockHdrCached(round) + hdr, err := cx.SigLedger.BlockHdr(round) if err != nil { return err } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index c7e73c0ba8..ee049d9d9c 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -175,8 +175,9 @@ func defaultAppParamsWithVersion(version uint64, txns ...transactions.SignedTxn) ep := NewAppEvalParams(transactions.WrapSignedTxnsWithAD(txns), makeTestProtoV(version), &transactions.SpecialAddresses{}) if ep != nil { // If supplied no apps, ep is nil. ep.Trace = &strings.Builder{} - ep.Ledger = NewLedger(nil) - ep.SigLedger = ep.Ledger + ledger := NewLedger(nil) + ep.Ledger = ledger + ep.SigLedger = ledger } return ep } @@ -6011,3 +6012,12 @@ pop int 1 `, 8) } + +func TestNoHeaderLedger(t *testing.T) { + partitiontest.PartitionTest(t) + + nhl := NoHeaderLedger{} + _, err := nhl.BlockHdr(1) + require.Error(t, err) + require.Equal(t, err, fmt.Errorf("no block header access")) +} diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 3016ae5298..a111eec131 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -215,8 +215,8 @@ func (l *Ledger) PrevTimestamp() int64 { return int64(rand.Uint32() + 1) } -// BlockHdrCached returns the block header for the given round, if it is available -func (l *Ledger) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) { +// BlockHdr returns the block header for the given round, if it is available +func (l *Ledger) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) { hdr := bookkeeping.BlockHeader{} // Return a fake seed that is different for each round seed := committee.Seed{} diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 9c7d001470..416c0e4c08 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -72,9 +72,6 @@ type DummyLedgerForSignature struct { badHdr bool } -func (d *DummyLedgerForSignature) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) { - return createDummyBlockHeader(), nil -} func (d *DummyLedgerForSignature) BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) { if d.badHdr { return bookkeeping.BlockHeader{}, fmt.Errorf("test error block hdr") diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index bb495f3f8f..0408034ae2 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -723,7 +723,7 @@ type accountUpdatesLedgerEvaluator struct { au *accountUpdates // ao is onlineAccounts for voters access ao *onlineAccounts - // txtail allows implementation of BlockHdrCached + // txtail allows BlockHdr to serve blockHdr without going to disk tail *txTail // prevHeader is the previous header to the current one. The usage of this is only in the context of initializeCaches where we iteratively // building the ledgercore.StateDelta, which requires a peek on the "previous" header information. @@ -758,17 +758,11 @@ func (aul *accountUpdatesLedgerEvaluator) BlockHdr(r basics.Round) (bookkeeping. if r == aul.prevHeader.Round { return aul.prevHeader, nil } - return bookkeeping.BlockHeader{}, ledgercore.ErrNoEntry{} -} - -// BlockHdrCached returns the header of the given round. We use the txTail -// tracker directly to avoid the tracker registry lock. -func (aul *accountUpdatesLedgerEvaluator) BlockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) { hdr, ok := aul.tail.blockHeader(r) - if !ok { - return bookkeeping.BlockHeader{}, fmt.Errorf("no cached header data for round %d", r) + if ok { + return hdr, nil } - return hdr, nil + return bookkeeping.BlockHeader{}, ledgercore.ErrNoEntry{} } // LatestTotals returns the totals of all accounts for the most recent round, as well as the round number diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index d938b0d530..658cb71330 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -2711,3 +2711,16 @@ func TestAcctUpdatesLookupStateDelta(t *testing.T) { require.Contains(t, data.Assets, aidx3) require.NotContains(t, data.Assets, aidx2) } + +func TestAccountUpdatesLedgerEvaluatorNoBlockHdr(t *testing.T) { + partitiontest.PartitionTest(t) + + aul := &accountUpdatesLedgerEvaluator{ + prevHeader: bookkeeping.BlockHeader{}, + tail: &txTail{}, + } + hdr, err := aul.BlockHdr(99) + require.Error(t, err) + require.Equal(t, ledgercore.ErrNoEntry{}, err) + require.Equal(t, bookkeeping.BlockHeader{}, hdr) +} diff --git a/ledger/blockHeaderCache.go b/ledger/blockHeaderCache.go deleted file mode 100644 index b0f27f78ec..0000000000 --- a/ledger/blockHeaderCache.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (C) 2019-2023 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package ledger - -import ( - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - - "github.com/algorand/go-deadlock" -) - -const latestHeaderCacheSize = 512 -const blockHeadersLRUCacheSize = 10 - -// blockHeaderCache is a wrapper for all block header cache mechanisms used within the Ledger. -type blockHeaderCache struct { - lruCache heapLRUCache - latestHeaderCache latestBlockHeaderCache -} - -type latestBlockHeaderCache struct { - blockHeaders [latestHeaderCacheSize]bookkeeping.BlockHeader - mutex deadlock.RWMutex -} - -func (c *blockHeaderCache) initialize() { - c.lruCache.maxEntries = blockHeadersLRUCacheSize -} - -func (c *blockHeaderCache) get(round basics.Round) (blockHeader bookkeeping.BlockHeader, exists bool) { - // check latestHeaderCache first - blockHeader, exists = c.latestHeaderCache.get(round) - if exists { - return - } - - // if not found in latestHeaderCache, check LRUCache - value, exists := c.lruCache.Get(round) - if exists { - blockHeader = value.(bookkeeping.BlockHeader) - } - - return -} - -func (c *blockHeaderCache) put(blockHeader bookkeeping.BlockHeader) { - c.latestHeaderCache.put(blockHeader) - c.lruCache.Put(blockHeader.Round, blockHeader) -} - -func (c *latestBlockHeaderCache) get(round basics.Round) (blockHeader bookkeeping.BlockHeader, exists bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - - idx := round % latestHeaderCacheSize - if round == 0 || c.blockHeaders[idx].Round != round { // blockHeader is empty or not requested round - return bookkeeping.BlockHeader{}, false - } - blockHeader = c.blockHeaders[idx] - - return blockHeader, true -} - -func (c *latestBlockHeaderCache) put(blockHeader bookkeeping.BlockHeader) { - c.mutex.Lock() - defer c.mutex.Unlock() - - idx := blockHeader.Round % latestHeaderCacheSize - if blockHeader.Round > c.blockHeaders[idx].Round { // provided blockHeader is more recent than cached one - c.blockHeaders[idx] = blockHeader - } -} diff --git a/ledger/blockHeaderCache_test.go b/ledger/blockHeaderCache_test.go deleted file mode 100644 index 6728e71c65..0000000000 --- a/ledger/blockHeaderCache_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (C) 2019-2023 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package ledger - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/test/partitiontest" -) - -func TestBlockHeaderCache(t *testing.T) { - partitiontest.PartitionTest(t) - a := require.New(t) - - var cache blockHeaderCache - cache.initialize() - for i := basics.Round(1024); i < 1024+latestHeaderCacheSize; i++ { - hdr := bookkeeping.BlockHeader{Round: i} - cache.put(hdr) - } - - rnd := basics.Round(120) - hdr := bookkeeping.BlockHeader{Round: rnd} - cache.put(hdr) - - _, exists := cache.get(rnd) - a.True(exists) - - _, exists = cache.lruCache.Get(rnd) - a.True(exists) - - _, exists = cache.latestHeaderCache.get(rnd) - a.False(exists) - - rnd = basics.Round(2048) - hdr = bookkeeping.BlockHeader{Round: rnd} - cache.put(hdr) - - _, exists = cache.latestHeaderCache.get(rnd) - a.True(exists) - - _, exists = cache.lruCache.Get(rnd) - a.True(exists) - -} - -func TestLatestBlockHeaderCache(t *testing.T) { - partitiontest.PartitionTest(t) - a := require.New(t) - - var cache latestBlockHeaderCache - for i := basics.Round(123); i < latestHeaderCacheSize; i++ { - hdr := bookkeeping.BlockHeader{Round: i} - cache.put(hdr) - } - - for i := basics.Round(0); i < 123; i++ { - _, exists := cache.get(i) - a.False(exists) - } - - for i := basics.Round(123); i < latestHeaderCacheSize; i++ { - hdr, exists := cache.get(i) - a.True(exists) - a.Equal(i, hdr.Round) - } -} - -func TestCacheSizeConsensus(t *testing.T) { - partitiontest.PartitionTest(t) - a := require.New(t) - - a.Equal(uint64(latestHeaderCacheSize), config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*2) -} diff --git a/ledger/eval/applications.go b/ledger/eval/applications.go index e126cfd309..5419bc9525 100644 --- a/ledger/eval/applications.go +++ b/ledger/eval/applications.go @@ -21,7 +21,6 @@ import ( "github.com/algorand/avm-abi/apps" "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -129,10 +128,6 @@ func (cs *roundCowState) SetLocal(addr basics.Address, appIdx basics.AppIndex, k return cs.setKey(addr, appIdx, false, key, value, accountIdx) } -func (cs *roundCowState) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) { - return cs.blockHdrCached(round) -} - func (cs *roundCowState) DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error { return cs.delKey(addr, appIdx, false, key, accountIdx) } diff --git a/ledger/eval/cow.go b/ledger/eval/cow.go index 8019dc69f7..d797a4a560 100644 --- a/ledger/eval/cow.go +++ b/ledger/eval/cow.go @@ -57,7 +57,6 @@ type roundCowParent interface { getCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) GetStateProofNextRound() basics.Round BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) - blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) getStorageCounts(addr basics.Address, aidx basics.AppIndex, global bool) (basics.StateSchema, error) // note: getStorageLimits is redundant with the other methods // and is provided to optimize state schema lookups @@ -250,10 +249,6 @@ func (cb *roundCowState) GetStateProofVerificationContext(stateProofLastAttested return cb.lookupParent.GetStateProofVerificationContext(stateProofLastAttestedRound) } -func (cb *roundCowState) blockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) { - return cb.lookupParent.blockHdrCached(r) -} - func (cb *roundCowState) incTxnCount() { cb.txnCount++ } diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index f0dde2b399..5fc72ff54e 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -40,7 +40,6 @@ import ( // LedgerForCowBase represents subset of Ledger functionality needed for cow business type LedgerForCowBase interface { BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) - BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error LookupWithoutRewards(basics.Round, basics.Address) (ledgercore.AccountData, basics.Round, error) LookupAsset(basics.Round, basics.Address, basics.AssetIndex) (ledgercore.AssetResource, error) @@ -342,10 +341,6 @@ func (x *roundCowBase) GetStateProofVerificationContext(stateProofLastAttestedRo return x.l.GetStateProofVerificationContext(stateProofLastAttestedRound) } -func (x *roundCowBase) blockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) { - return x.l.BlockHdrCached(r) -} - func (x *roundCowBase) allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) { // For global, check if app params exist if global { diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 783f975bbc..b916558314 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -940,10 +940,6 @@ func (ledger *evalTestLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeade return block.BlockHeader, nil } -func (ledger *evalTestLedger) BlockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) { - return ledger.BlockHdr(rnd) -} - func (ledger *evalTestLedger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { return nil, errors.New("untested code path") } @@ -1041,10 +1037,6 @@ func (l *testCowBaseLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, err return bookkeeping.BlockHeader{}, errors.New("not implemented") } -func (l *testCowBaseLedger) BlockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) { - return l.BlockHdr(rnd) -} - func (l *testCowBaseLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { return errors.New("not implemented") } diff --git a/ledger/eval/prefetcher/prefetcher_alignment_test.go b/ledger/eval/prefetcher/prefetcher_alignment_test.go index efb9e683bf..81ce168d43 100644 --- a/ledger/eval/prefetcher/prefetcher_alignment_test.go +++ b/ledger/eval/prefetcher/prefetcher_alignment_test.go @@ -97,10 +97,6 @@ func (l *prefetcherAlignmentTestLedger) BlockHdr(round basics.Round) (bookkeepin fmt.Errorf("BlockHdr() round %d not supported", round) } -func (l *prefetcherAlignmentTestLedger) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) { - return l.BlockHdr(round) -} - func (l *prefetcherAlignmentTestLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { return nil } diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go index 5ac28ce814..1caa7d8ef5 100644 --- a/ledger/evalindexer.go +++ b/ledger/evalindexer.go @@ -45,7 +45,7 @@ type indexerLedgerForEval interface { LatestTotals() (ledgercore.AccountTotals, error) LookupKv(basics.Round, string) ([]byte, error) - BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) + BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) } // FoundAddress is a wrapper for an address and a boolean. @@ -92,11 +92,6 @@ func (l indexerLedgerConnector) BlockHdr(round basics.Round) (bookkeeping.BlockH return l.il.LatestBlockHdr() } -// BlockHdrCached is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) { - return l.il.BlockHdrCached(round) -} - // CheckDup is part of LedgerForEvaluator interface. func (l indexerLedgerConnector) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { // This function is not used by evaluator. diff --git a/ledger/evalindexer_test.go b/ledger/evalindexer_test.go index d5636f27ca..8209bb9282 100644 --- a/ledger/evalindexer_test.go +++ b/ledger/evalindexer_test.go @@ -47,8 +47,8 @@ func (il indexerLedgerForEvalImpl) LatestBlockHdr() (bookkeeping.BlockHeader, er return il.l.BlockHdr(il.latestRound) } -func (il indexerLedgerForEvalImpl) BlockHdrCached(round basics.Round) (bookkeeping.BlockHeader, error) { - return il.l.BlockHdrCached(round) +func (il indexerLedgerForEvalImpl) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) { + return il.l.BlockHdr(round) } // The value of the returned map is nil iff the account was not found. diff --git a/ledger/ledger.go b/ledger/ledger.go index dd54529ee6..6c02951a92 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -91,8 +91,6 @@ type Ledger struct { trackers trackerRegistry trackerMu deadlock.RWMutex - headerCache blockHeaderCache - // verifiedTxnCache holds all the verified transactions state verifiedTxnCache verify.VerifiedTransactionCache @@ -136,8 +134,6 @@ func OpenLedger( tracer: tracer, } - l.headerCache.initialize() - defer func() { if err != nil { l.Close() @@ -628,8 +624,6 @@ func (l *Ledger) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) (basi // CheckDup return whether a transaction is a duplicate one. func (l *Ledger) CheckDup(currentProto config.ConsensusParams, current basics.Round, firstValid basics.Round, lastValid basics.Round, txid transactions.Txid, txl ledgercore.Txlease) error { - l.trackerMu.RLock() - defer l.trackerMu.RUnlock() return l.txTail.checkDup(currentProto, current, firstValid, lastValid, txid, txl) } @@ -654,16 +648,22 @@ func (l *Ledger) Block(rnd basics.Round) (blk bookkeeping.Block, err error) { // BlockHdr returns the BlockHeader of the block for round rnd. func (l *Ledger) BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) { - blk, exists := l.headerCache.get(rnd) - if exists { - return - } - blk, err = l.blockQ.getBlockHdr(rnd) - if err == nil { - l.headerCache.put(blk) + // Expected availability range in txTail.blockHeader is [Latest - MaxTxnLife, Latest] + // allowing (MaxTxnLife + 1) = 1001 rounds back loopback. + // The depth besides the MaxTxnLife is controlled by DeeperBlockHeaderHistory parameter + // and currently set to 1. + // Explanation: + // Clients are expected to query blocks at rounds (txn.LastValid - (MaxTxnLife + 1)), + // and because a txn is alive when the current round <= txn.LastValid + // and valid if txn.LastValid - txn.FirstValid <= MaxTxnLife + // the deepest lookup happens when txn.LastValid == current => txn.LastValid == Latest + 1 + // that gives Latest + 1 - (MaxTxnLife + 1) = Latest - MaxTxnLife as the first round to be accessible. + hdr, ok := l.txTail.blockHeader(rnd) + if !ok { + hdr, err = l.blockQ.getBlockHdr(rnd) } - return + return hdr, err } // EncodedBlockCert returns the encoded block and the corresponding encoded certificate of the block for round rnd. @@ -712,7 +712,6 @@ func (l *Ledger) AddValidatedBlock(vb ledgercore.ValidatedBlock, cert agreement. if err != nil { return err } - l.headerCache.put(blk.BlockHeader) l.trackers.newBlock(blk, vb.Delta()) l.log.Debugf("ledger.AddValidatedBlock: added blk %d", blk.Round()) return nil @@ -755,27 +754,6 @@ func (l *Ledger) GenesisAccounts() map[basics.Address]basics.AccountData { return l.genesisAccounts } -// BlockHdrCached returns the block header if available. -// Expected availability range is [Latest - MaxTxnLife, Latest] -// allowing (MaxTxnLife + 1) = 1001 rounds back loopback. -// The depth besides the MaxTxnLife is controlled by DeeperBlockHeaderHistory parameter -// and currently set to 1. -// Explanation: -// Clients are expected to query blocks at rounds (txn.LastValid - (MaxTxnLife + 1)), -// and because a txn is alive when the current round <= txn.LastValid -// and valid if txn.LastValid - txn.FirstValid <= MaxTxnLife -// the deepest lookup happens when txn.LastValid == current => txn.LastValid == Latest + 1 -// that gives Latest + 1 - (MaxTxnLife + 1) = Latest - MaxTxnLife as the first round to be accessible. -func (l *Ledger) BlockHdrCached(rnd basics.Round) (hdr bookkeeping.BlockHeader, err error) { - l.trackerMu.RLock() - defer l.trackerMu.RUnlock() - hdr, ok := l.txTail.blockHeader(rnd) - if !ok { - err = fmt.Errorf("no cached header data for round %d", rnd) - } - return hdr, err -} - // GetCatchpointCatchupState returns the current state of the catchpoint catchup. func (l *Ledger) GetCatchpointCatchupState(ctx context.Context) (state CatchpointCatchupState, err error) { return MakeCatchpointCatchupAccessor(l, l.log).GetState(ctx) diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index f7a847a11b..5edb6d20e1 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -1385,46 +1385,6 @@ func testLedgerRegressionFaultyLeaseFirstValidCheck2f3880f7(t *testing.T, versio } } -func TestLedgerBlockHdrCaching(t *testing.T) { - partitiontest.PartitionTest(t) - a := require.New(t) - - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState := getInitState() - const inMem = true - cfg := config.GetDefaultLocal() - cfg.Archival = true - log := logging.TestingLog(t) - log.SetLevel(logging.Info) - l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) - a.NoError(err) - defer l.Close() - - blk := genesisInitState.Block - - for i := 0; i < 1024; i++ { - blk.BlockHeader.Round++ - blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000) - err := l.AddBlock(blk, agreement.Certificate{}) - a.NoError(err) - - hdr, err := l.BlockHdr(blk.BlockHeader.Round) - a.NoError(err) - a.Equal(blk.BlockHeader, hdr) - } - - rnd := basics.Round(128) - hdr, err := l.BlockHdr(rnd) // should update LRU cache but not latestBlockHeaderCache - a.NoError(err) - a.Equal(rnd, hdr.Round) - - _, exists := l.headerCache.lruCache.Get(rnd) - a.True(exists) - - _, exists = l.headerCache.latestHeaderCache.get(rnd) - a.False(exists) -} - func BenchmarkLedgerBlockHdrCaching(b *testing.B) { benchLedgerCache(b, 1024-256+1) } @@ -2629,7 +2589,7 @@ func TestLedgerTxTailCachedBlockHeaders(t *testing.T) { latest := l.Latest() for i := latest - basics.Round(proto.MaxTxnLife); i <= latest; i++ { - blk, err := l.BlockHdrCached(i) + blk, err := l.BlockHdr(i) require.NoError(t, err) require.Equal(t, blk.Round, i) } @@ -2643,13 +2603,13 @@ func TestLedgerTxTailCachedBlockHeaders(t *testing.T) { start := dbRound - basics.Round(proto.MaxTxnLife) end := latest - basics.Round(proto.MaxTxnLife) for i := start; i < end; i++ { - blk, err := l.BlockHdrCached(i) + blk, err := l.BlockHdr(i) require.NoError(t, err) require.Equal(t, blk.Round, i) } - _, err = l.BlockHdrCached(start - 1) - require.Error(t, err) + _, ok := l.txTail.blockHeader(start - 1) + require.False(t, ok) } // TestLedgerKeyregFlip generates keyreg transactions for flipping genesis accounts state. diff --git a/ledger/lrukv.go b/ledger/lrukv.go index 8955d3d2bc..8c407a9fc5 100644 --- a/ledger/lrukv.go +++ b/ledger/lrukv.go @@ -79,7 +79,7 @@ func (m *lruKV) read(key string) (data trackerdb.PersistedKVData, has bool) { func (m *lruKV) flushPendingWrites() { pendingEntriesCount := len(m.pendingKVs) if pendingEntriesCount >= m.pendingWritesWarnThreshold { - m.log.Warnf("lruKV: number of entries in pendingKVs(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold) + m.log.Infof("lruKV: number of entries in pendingKVs(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold) } for ; pendingEntriesCount > 0; pendingEntriesCount-- { select { diff --git a/ledger/lrukv_test.go b/ledger/lrukv_test.go index 0b0347e240..35345091b2 100644 --- a/ledger/lrukv_test.go +++ b/ledger/lrukv_test.go @@ -18,6 +18,7 @@ package ledger import ( "fmt" + "strings" "testing" "time" @@ -162,8 +163,10 @@ type lruKVTestLogger struct { warnMsgCount int } -func (cl *lruKVTestLogger) Warnf(s string, args ...interface{}) { - cl.warnMsgCount++ +func (cl *lruKVTestLogger) Infof(s string, args ...interface{}) { + if strings.Contains(s, "exceed the warning threshold of") { + cl.warnMsgCount++ + } } func TestLRUKVPendingWritesWarning(t *testing.T) { diff --git a/ledger/lruresources.go b/ledger/lruresources.go index 9886b29b12..f0a536350e 100644 --- a/ledger/lruresources.go +++ b/ledger/lruresources.go @@ -103,7 +103,7 @@ func (m *lruResources) readAll(addr basics.Address) (ret []trackerdb.PersistedRe func (m *lruResources) flushPendingWrites() { pendingEntriesCount := len(m.pendingResources) if pendingEntriesCount >= m.pendingWritesWarnThreshold { - m.log.Warnf("lruResources: number of entries in pendingResources(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold) + m.log.Infof("lruResources: number of entries in pendingResources(%d) exceed the warning threshold of %d", pendingEntriesCount, m.pendingWritesWarnThreshold) } outer: diff --git a/ledger/lruresources_test.go b/ledger/lruresources_test.go index 9268889a58..b678ea742b 100644 --- a/ledger/lruresources_test.go +++ b/ledger/lruresources_test.go @@ -18,6 +18,7 @@ package ledger import ( "encoding/binary" + "strings" "testing" "time" @@ -180,8 +181,10 @@ type lruResourcesTestLogger struct { warnMsgCount int } -func (cl *lruResourcesTestLogger) Warnf(s string, args ...interface{}) { - cl.warnMsgCount++ +func (cl *lruResourcesTestLogger) Infof(s string, args ...interface{}) { + if strings.Contains(s, "exceed the warning threshold of") { + cl.warnMsgCount++ + } } func TestLRUResourcesPendingWritesWarning(t *testing.T) { diff --git a/ledger/roundlru.go b/ledger/roundlru.go deleted file mode 100644 index 43d8639872..0000000000 --- a/ledger/roundlru.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2019-2023 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package ledger - -import ( - "container/heap" - - "github.com/algorand/go-deadlock" - - "github.com/algorand/go-algorand/data/basics" -) - -type lruEntry struct { - useIndex int - r basics.Round - x interface{} -} - -type lruHeap struct { - heap []lruEntry - lookup map[basics.Round]int -} - -// Len is part of heap.Interface -func (h *lruHeap) Len() int { - return len(h.heap) -} - -// Less reports whether the element with -// index i should sort before the element with index j. -func (h *lruHeap) Less(i, j int) bool { - return h.heap[i].useIndex < h.heap[j].useIndex -} - -// Swap swaps the elements with indexes i and j. -func (h *lruHeap) Swap(i, j int) { - t := h.heap[i] - h.heap[i] = h.heap[j] - h.heap[j] = t - h.lookup[h.heap[i].r] = i - h.lookup[h.heap[j].r] = j -} -func (h *lruHeap) Push(x interface{}) { - // add x as element Len() - xv := x.(lruEntry) - h.heap = append(h.heap, xv) - h.lookup[xv.r] = len(h.heap) - 1 -} -func (h *lruHeap) Pop() interface{} { - // remove and return element Len() - 1. - oldlen := len(h.heap) - out := h.heap[oldlen-1] - h.heap = h.heap[:oldlen-1] - delete(h.lookup, out.r) - return out -} - -type heapLRUCache struct { - entries lruHeap - lock deadlock.Mutex - nextUseIndex int - maxEntries int -} - -func (hlc *heapLRUCache) Get(r basics.Round) (ob interface{}, exists bool) { - hlc.lock.Lock() - defer hlc.lock.Unlock() - if i, present := hlc.entries.lookup[r]; present { - out := hlc.entries.heap[i].x - hlc.entries.heap[i].useIndex = hlc.nextUseIndex - hlc.inc() - heap.Fix(&hlc.entries, i) - return out, true - } - return nil, false -} -func (hlc *heapLRUCache) Put(r basics.Round, data interface{}) { - hlc.lock.Lock() - defer hlc.lock.Unlock() - if hlc.entries.heap == nil { - hlc.entries.heap = make([]lruEntry, 1) - hlc.entries.heap[0] = lruEntry{hlc.nextUseIndex, r, data} - hlc.inc() - hlc.entries.lookup = make(map[basics.Round]int) - hlc.entries.lookup[r] = 0 - return - } - if i, present := hlc.entries.lookup[r]; present { - // update data, but don't adjust LRU order - hlc.entries.heap[i].x = data - return - } - heap.Push(&hlc.entries, lruEntry{hlc.nextUseIndex, r, data}) - for len(hlc.entries.heap) > hlc.maxEntries { - heap.Remove(&hlc.entries, 0) - } - hlc.inc() -} - -// MaxInt is the maximum int which might be int32 or int64 -const MaxInt = int((^uint(0)) >> 1) - -func (hlc *heapLRUCache) inc() { - hlc.nextUseIndex++ - if hlc.nextUseIndex == MaxInt { - hlc.reIndex() - } -} -func (hlc *heapLRUCache) reIndex() { - if len(hlc.entries.heap) == 0 { - return - } - minprio := hlc.entries.heap[0].useIndex - maxprio := hlc.entries.heap[0].useIndex - for i := 1; i < len(hlc.entries.heap); i++ { - xp := hlc.entries.heap[i].useIndex - if xp < minprio { - minprio = xp - } - if xp > maxprio { - maxprio = xp - } - } - for i := range hlc.entries.heap { - hlc.entries.heap[i].useIndex -= minprio - } - hlc.nextUseIndex = maxprio + 1 - minprio -} diff --git a/ledger/roundlru_test.go b/ledger/roundlru_test.go deleted file mode 100644 index 8033f34399..0000000000 --- a/ledger/roundlru_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (C) 2019-2023 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package ledger - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/test/partitiontest" -) - -func getEq(t *testing.T, cache *heapLRUCache, r basics.Round, expected string) { - got, exists := cache.Get(r) - if !exists { - t.Fatalf("expected value for cache[%v] but not present", r) - return - } - actual := got.(string) - if actual != expected { - t.Fatalf("expected %v but got %v for %v", expected, actual, r) - } -} - -func getNone(t *testing.T, cache *heapLRUCache, r basics.Round) { - got, exists := cache.Get(r) - if exists { - t.Fatalf("expected none for cache[%v] but got %v", r, got) - return - } -} - -func TestRoundLRUBasic(t *testing.T) { - partitiontest.PartitionTest(t) - - cache := heapLRUCache{maxEntries: 3} - cache.Put(1, "one") - cache.Put(2, "two") - cache.Put(3, "three") - getEq(t, &cache, 1, "one") - getEq(t, &cache, 2, "two") - getEq(t, &cache, 3, "three") - cache.Put(4, "four") - getNone(t, &cache, 1) - getEq(t, &cache, 3, "three") - cache.Put(5, "five") - cache.Put(6, "six") - getEq(t, &cache, 3, "three") - getNone(t, &cache, 2) - getNone(t, &cache, 4) -} - -func TestRoundLRUReIndex(t *testing.T) { - partitiontest.PartitionTest(t) - - cache := heapLRUCache{ - entries: lruHeap{ - heap: []lruEntry{ - { - useIndex: MaxInt - 2, - }, - { - useIndex: MaxInt - 1, - }, - { - useIndex: MaxInt - 3, - }, - }, - }, - maxEntries: 3, - nextUseIndex: MaxInt - 1, - } - - cache.inc() - - require.Equal(t, 3, cache.nextUseIndex) - require.Equal(t, 1, cache.entries.heap[0].useIndex) - require.Equal(t, 2, cache.entries.heap[1].useIndex) - require.Equal(t, 0, cache.entries.heap[2].useIndex) -} diff --git a/ledger/tracker.go b/ledger/tracker.go index ebed56d785..8cfe71aff0 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -282,6 +282,8 @@ func (dcc deferredCommitContext) newBase() basics.Round { var errMissingAccountUpdateTracker = errors.New("initializeTrackerCaches : called without a valid accounts update tracker") func (tr *trackerRegistry) initialize(l ledgerForTracker, trackers []ledgerTracker, cfg config.Local) (err error) { + tr.mu.Lock() + defer tr.mu.Unlock() tr.dbs = l.trackerDB() tr.log = l.trackerLog() diff --git a/ledger/txtail.go b/ledger/txtail.go index 7d71ea27ef..31e44be77a 100644 --- a/ledger/txtail.go +++ b/ledger/txtail.go @@ -76,7 +76,8 @@ type txTail struct { // lowestBlockHeaderRound is the lowest round in blockHeaderData, used as a starting point for old entries removal lowestBlockHeaderRound basics.Round - // tailMu is the synchronization mutex for accessing roundTailHashes, roundTailSerializedDeltas and blockHeaderData. + // tailMu is the synchronization mutex for accessing internal data including + // lastValid, recent, lowWaterMark, roundTailHashes, roundTailSerializedDeltas and blockHeaderData. tailMu deadlock.RWMutex lastValid map[basics.Round]map[transactions.Txid]struct{} // map tx.LastValid -> tx confirmed set @@ -90,6 +91,9 @@ type txTail struct { } func (t *txTail) loadFromDisk(l ledgerForTracker, dbRound basics.Round) error { + t.tailMu.Lock() + defer t.tailMu.Unlock() + t.log = l.trackerLog() var roundData []*trackerdb.TxTailRound @@ -191,6 +195,9 @@ func (t *txTail) close() { func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { rnd := blk.Round() + t.tailMu.Lock() + defer t.tailMu.Unlock() + if _, has := t.recent[rnd]; has { // Repeat, ignore return @@ -202,7 +209,11 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { tail.Hdr = blk.BlockHeader for txid, txnInc := range delta.Txids { - t.putLV(txnInc.LastValid, txid) + if _, ok := t.lastValid[txnInc.LastValid]; !ok { + t.lastValid[txnInc.LastValid] = make(map[transactions.Txid]struct{}) + } + t.lastValid[txnInc.LastValid][txid] = struct{}{} + tail.TxnIDs[txnInc.Intra] = txid tail.LastValid[txnInc.Intra] = txnInc.LastValid if blk.Payset[txnInc.Intra].Txn.Lease != [32]byte{} { @@ -215,8 +226,6 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { } encodedTail, tailHash := tail.Encode() - t.tailMu.Lock() - defer t.tailMu.Unlock() t.recent[rnd] = roundLeases{ txleases: delta.Txleases, proto: config.Consensus[blk.CurrentProtocol], @@ -229,6 +238,9 @@ func (t *txTail) newBlock(blk bookkeeping.Block, delta ledgercore.StateDelta) { } func (t *txTail) committedUpTo(rnd basics.Round) (retRound, lookback basics.Round) { + t.tailMu.Lock() + defer t.tailMu.Unlock() + proto := t.recent[rnd].proto maxlife := basics.Round(proto.MaxTxnLife) @@ -333,6 +345,12 @@ func (t errTxTailMissingRound) Error() string { // checkDup test to see if the given transaction id/lease already exists. It returns nil if neither exists, or // TransactionInLedgerError / LeaseInLedgerError respectively. func (t *txTail) checkDup(proto config.ConsensusParams, current basics.Round, firstValid basics.Round, lastValid basics.Round, txid transactions.Txid, txl ledgercore.Txlease) error { + // txTail does not use l.trackerMu, instead uses t.tailMu to make it thread-safe + // t.tailMu is sufficient because the state of txTail does not depend on any outside data field + + t.tailMu.RLock() + defer t.tailMu.RUnlock() + if lastValid < t.lowWaterMark { return &errTxTailMissingRound{round: lastValid} } @@ -359,13 +377,6 @@ func (t *txTail) checkDup(proto config.ConsensusParams, current basics.Round, fi return nil } -func (t *txTail) putLV(lastValid basics.Round, id transactions.Txid) { - if _, ok := t.lastValid[lastValid]; !ok { - t.lastValid[lastValid] = make(map[transactions.Txid]struct{}) - } - t.lastValid[lastValid][id] = struct{}{} -} - func (t *txTail) recentTailHash(offset uint64, retainSize uint64) (crypto.Digest, error) { // prepare a buffer to hash. buffer := make([]byte, (retainSize)*crypto.DigestSize) @@ -387,8 +398,5 @@ func (t *txTail) blockHeader(rnd basics.Round) (bookkeeping.BlockHeader, bool) { t.tailMu.RLock() defer t.tailMu.RUnlock() hdr, ok := t.blockHeaderData[rnd] - if !ok { - t.log.Warnf("txtail failed to fetch blockHeader from rnd: %d", rnd) - } return hdr, ok } diff --git a/network/p2p/dnsaddr/resolve.go b/network/p2p/dnsaddr/resolve.go index 9ac3010415..2289033aee 100644 --- a/network/p2p/dnsaddr/resolve.go +++ b/network/p2p/dnsaddr/resolve.go @@ -69,7 +69,7 @@ func MultiaddrsFromResolver(domain string, controller *MultiaddrDNSResolveContro return nil, fmt.Errorf("unable to construct multiaddr for %s : %v", domain, err) } var resolved []multiaddr.Multiaddr - err = Iterate(dnsaddr, controller, func(dnsaddr multiaddr.Multiaddr, entries []multiaddr.Multiaddr) error { + err = Iterate(dnsaddr, controller, func(_ multiaddr.Multiaddr, entries []multiaddr.Multiaddr) error { for _, maddr := range entries { if !isDnsaddr(maddr) { resolved = append(resolved, maddr) diff --git a/node/node.go b/node/node.go index b3b508e91a..87ffd3e2a4 100644 --- a/node/node.go +++ b/node/node.go @@ -491,8 +491,19 @@ func (node *AlgorandFullNode) BroadcastInternalSignedTxGroup(txgroup []transacti return node.broadcastSignedTxGroup(txgroup) } +var broadcastTxSucceeded = metrics.MakeCounter(metrics.BroadcastSignedTxGroupSucceeded) +var broadcastTxFailed = metrics.MakeCounter(metrics.BroadcastSignedTxGroupFailed) + // broadcastSignedTxGroup broadcasts a transaction group that has already been signed. func (node *AlgorandFullNode) broadcastSignedTxGroup(txgroup []transactions.SignedTxn) (err error) { + defer func() { + if err != nil { + broadcastTxFailed.Inc(nil) + } else { + broadcastTxSucceeded.Inc(nil) + } + }() + lastRound := node.ledger.Latest() var b bookkeeping.BlockHeader b, err = node.ledger.BlockHdr(lastRound) diff --git a/stateproof/abstractions.go b/stateproof/abstractions.go index 552acd0b82..67f4731882 100644 --- a/stateproof/abstractions.go +++ b/stateproof/abstractions.go @@ -18,6 +18,7 @@ package stateproof import ( "context" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" diff --git a/tools/block-generator/generator/generate.go b/tools/block-generator/generator/generate.go index 6e9d9fc444..3a899a33b4 100644 --- a/tools/block-generator/generator/generate.go +++ b/tools/block-generator/generator/generate.go @@ -27,15 +27,15 @@ import ( "time" cconfig "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/ledger/ledgercore" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/rpcs" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" txn "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/rpcs" ) // ---- templates ---- @@ -55,14 +55,19 @@ var clearSwap string // ---- constructors ---- // MakeGenerator initializes the Generator object. -func MakeGenerator(dbround uint64, bkGenesis bookkeeping.Genesis, config GenerationConfig, verbose bool) (Generator, error) { +func MakeGenerator(log logging.Logger, dbround uint64, bkGenesis bookkeeping.Genesis, config GenerationConfig, verbose bool) (Generator, error) { if err := config.validateWithDefaults(false); err != nil { return nil, fmt.Errorf("invalid generator configuration: %w", err) } + if log == nil { + log = logging.Base() + } + var proto protocol.ConsensusVersion = "future" gen := &generator{ verbose: verbose, + log: log, config: config, protocol: proto, params: cconfig.Consensus[proto], diff --git a/tools/block-generator/generator/generate_test.go b/tools/block-generator/generator/generate_test.go index 4ae79d9e71..29a2613d64 100644 --- a/tools/block-generator/generator/generate_test.go +++ b/tools/block-generator/generator/generate_test.go @@ -24,15 +24,17 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func makePrivateGenerator(t *testing.T, round uint64, genesis bookkeeping.Genesis) *generator { @@ -45,7 +47,7 @@ func makePrivateGenerator(t *testing.T, round uint64, genesis bookkeeping.Genesi AssetCreateFraction: 1.0, } cfg.validateWithDefaults(true) - publicGenerator, err := MakeGenerator(round, genesis, cfg, true) + publicGenerator, err := MakeGenerator(logging.Base(), round, genesis, cfg, true) require.NoError(t, err) return publicGenerator.(*generator) } @@ -355,7 +357,7 @@ func TestAppBoxesOptin(t *testing.T) { paySiblingTxn := sgnTxns[1].Txn require.Equal(t, protocol.PaymentTx, paySiblingTxn.Type) - + g.finishRound() // 2nd attempt to optin (with new sender) doesn't get replaced g.startRound() @@ -723,21 +725,21 @@ func TestCumulativeEffects(t *testing.T) { partitiontest.PartitionTest(t) report := Report{ - TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)}, - TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)}, - TxTypeID("pay_pay"): {GenerationCount: uint64(999)}, - TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)}, - TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)}, + TxTypeID("app_boxes_optin"): {GenerationCount: uint64(42)}, + TxTypeID("app_boxes_create"): {GenerationCount: uint64(1337)}, + TxTypeID("pay_pay"): {GenerationCount: uint64(999)}, + TxTypeID("asset_optin_total"): {GenerationCount: uint64(13)}, + TxTypeID("app_boxes_call"): {GenerationCount: uint64(413)}, } expectedEffectsReport := EffectsReport{ - "app_boxes_optin": uint64(42), - "app_boxes_create": uint64(1337), - "pay_pay": uint64(999), - "asset_optin_total": uint64(13), - "app_boxes_call": uint64(413), - "effect_payment_sibling": uint64(42) + uint64(1337), - "effect_inner_tx": uint64(2 * 42), + "app_boxes_optin": uint64(42), + "app_boxes_create": uint64(1337), + "pay_pay": uint64(999), + "asset_optin_total": uint64(13), + "app_boxes_call": uint64(413), + "effect_payment_sibling": uint64(42) + uint64(1337), + "effect_inner_tx": uint64(2 * 42), } require.Equal(t, expectedEffectsReport, CumulativeEffects(report)) @@ -772,7 +774,7 @@ func TestCountInners(t *testing.T) { InnerTxns: []transactions.SignedTxnWithAD{ { ApplyData: transactions.ApplyData{ - EvalDelta: transactions.EvalDelta{ + EvalDelta: transactions.EvalDelta{ InnerTxns: []transactions.SignedTxnWithAD{{}, {}}, }, }, @@ -793,4 +795,3 @@ func TestCountInners(t *testing.T) { }) } } - diff --git a/tools/block-generator/generator/generator_ledger.go b/tools/block-generator/generator/generator_ledger.go index c129065668..97fed9b344 100644 --- a/tools/block-generator/generator/generator_ledger.go +++ b/tools/block-generator/generator/generator_ledger.go @@ -31,7 +31,6 @@ import ( "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/ledger/eval" "github.com/algorand/go-algorand/ledger/ledgercore" - "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/rpcs" ) @@ -40,7 +39,7 @@ import ( func (g *generator) setBlockHeader(cert *rpcs.EncodedBlockCert) { cert.Block.BlockHeader = bookkeeping.BlockHeader{ Round: basics.Round(g.round), - TxnCounter: g.txnCounter, + TxnCounter: g.txnCounter, Branch: bookkeeping.BlockHash{}, Seed: committee.Seed{}, TxnCommitments: bookkeeping.TxnCommitments{NativeSha512_256Commitment: crypto.Digest{}}, @@ -63,10 +62,9 @@ func (g *generator) setBlockHeader(cert *rpcs.EncodedBlockCert) { } } - // ---- ledger simulation and introspection ---- -// initializeLedger creates a new ledger +// initializeLedger creates a new ledger func (g *generator) initializeLedger() { genBal := convertToGenesisBalances(g.balances) // add rewards pool with min balance @@ -85,7 +83,7 @@ func (g *generator) initializeLedger() { } else { prefix = g.genesisID } - l, err := ledger.OpenLedger(logging.Base(), prefix, true, ledgercore.InitState{ + l, err := ledger.OpenLedger(g.log, prefix, true, ledgercore.InitState{ Block: block, Accounts: bal.Balances, GenesisHash: g.genesisHash, diff --git a/tools/block-generator/generator/generator_types.go b/tools/block-generator/generator/generator_types.go index c0ff24b4c0..6685ffe7c8 100644 --- a/tools/block-generator/generator/generator_types.go +++ b/tools/block-generator/generator/generator_types.go @@ -26,6 +26,7 @@ import ( "github.com/algorand/go-algorand/data/bookkeeping" txn "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" ) @@ -42,6 +43,7 @@ type Generator interface { type generator struct { verbose bool + log logging.Logger config GenerationConfig @@ -52,7 +54,7 @@ type generator struct { numAccounts uint64 // Block stuff - round uint64 + round uint64 txnCounter uint64 prevBlockHash string timestamp int64 diff --git a/tools/block-generator/generator/server.go b/tools/block-generator/generator/server.go index 2aac4a4552..4de0d08322 100644 --- a/tools/block-generator/generator/server.go +++ b/tools/block-generator/generator/server.go @@ -24,6 +24,7 @@ import ( "time" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/tools/block-generator/util" ) @@ -32,7 +33,7 @@ func MakeServer(configFile string, addr string) (*http.Server, Generator) { noOp := func(next http.Handler) http.Handler { return next } - return MakeServerWithMiddleware(0, "", configFile, false, addr, noOp) + return MakeServerWithMiddleware(nil, 0, "", configFile, false, addr, noOp) } // BlocksMiddleware is a middleware for the blocks endpoint. @@ -41,7 +42,7 @@ type BlocksMiddleware func(next http.Handler) http.Handler // MakeServerWithMiddleware allows injecting a middleware for the blocks handler. // This is needed to simplify tests by stopping block production while validation // is done on the data. -func MakeServerWithMiddleware(dbround uint64, genesisFile string, configFile string, verbose bool, addr string, blocksMiddleware BlocksMiddleware) (*http.Server, Generator) { +func MakeServerWithMiddleware(log logging.Logger, dbround uint64, genesisFile string, configFile string, verbose bool, addr string, blocksMiddleware BlocksMiddleware) (*http.Server, Generator) { cfg, err := initializeConfigFile(configFile) util.MaybeFail(err, "problem loading config file. Use '--config' or create a config file.") var bkGenesis bookkeeping.Genesis @@ -50,7 +51,7 @@ func MakeServerWithMiddleware(dbround uint64, genesisFile string, configFile str // TODO: consider using bkGenesis to set cfg.NumGenesisAccounts and cfg.GenesisAccountInitialBalance util.MaybeFail(err, "Failed to parse genesis file '%s'", genesisFile) } - gen, err := MakeGenerator(dbround, bkGenesis, cfg, verbose) + gen, err := MakeGenerator(log, dbround, bkGenesis, cfg, verbose) util.MaybeFail(err, "Failed to make generator with config file '%s'", configFile) mux := http.NewServeMux() diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod index e3036565d1..6eadc0421a 100644 --- a/tools/block-generator/go.mod +++ b/tools/block-generator/go.mod @@ -6,7 +6,7 @@ go 1.20 require ( github.com/algorand/avm-abi v0.2.0 - github.com/algorand/go-algorand v0.0.0-00010101000000-000000000000 + github.com/algorand/go-algorand v0.0.0 github.com/algorand/go-codec/codec v1.1.10 github.com/algorand/go-deadlock v0.2.2 github.com/lib/pq v1.10.9 diff --git a/tools/block-generator/runner/run.go b/tools/block-generator/runner/run.go index 626a4493a6..31c83dc91b 100644 --- a/tools/block-generator/runner/run.go +++ b/tools/block-generator/runner/run.go @@ -35,14 +35,18 @@ import ( "text/template" "time" + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/tools/block-generator/generator" "github.com/algorand/go-algorand/tools/block-generator/util" - "github.com/algorand/go-deadlock" ) //go:embed template/conduit.yml.tmpl var conduitConfigTmpl string +const pad = " " + // Args are all the things needed to run a performance test. type Args struct { // Path is a directory when passed to RunBatch, otherwise a file path. @@ -107,7 +111,9 @@ func Run(args Args) error { } runnerArgs := args runnerArgs.Path = path - fmt.Printf("Running test for configuration '%s'\n", path) + fmt.Println("----------------------------------------") + fmt.Printf("%sRunning test for configuration: %s\n", pad, info.Name()) + fmt.Println("----------------------------------------") return runnerArgs.run(reportDirectory) }) if err != nil { @@ -121,7 +127,8 @@ func (r *Args) run(reportDirectory string) error { baseName := filepath.Base(r.Path) baseNameNoExt := strings.TrimSuffix(baseName, filepath.Ext(baseName)) reportfile := path.Join(reportDirectory, fmt.Sprintf("%s.report", baseNameNoExt)) - logfile := path.Join(reportDirectory, fmt.Sprintf("%s.conduit-log", baseNameNoExt)) + conduitlogfile := path.Join(reportDirectory, fmt.Sprintf("%s.conduit-log", baseNameNoExt)) + ledgerlogfile := path.Join(reportDirectory, fmt.Sprintf("%s.ledger-log", baseNameNoExt)) dataDir := path.Join(reportDirectory, fmt.Sprintf("%s_data", baseNameNoExt)) // create the data directory. if err := os.Mkdir(dataDir, os.ModeDir|os.ModePerm); err != nil { @@ -155,35 +162,38 @@ func (r *Args) run(reportDirectory string) error { } else if err != nil { return fmt.Errorf("getNextRound err: %w", err) } + fmt.Printf("%sPostgreSQL next round: %d\n", pad, nextRound) } // Start services algodNet := fmt.Sprintf("localhost:%d", 11112) metricsNet := fmt.Sprintf("localhost:%d", r.MetricsPort) - generatorShutdownFunc, _ := startGenerator(r.Path, nextRound, r.GenesisFile, r.RunnerVerbose, algodNet, blockMiddleware) + generatorShutdownFunc, _ := startGenerator(ledgerlogfile, r.Path, nextRound, r.GenesisFile, r.RunnerVerbose, algodNet, blockMiddleware) defer func() { // Shutdown generator. + fmt.Printf("%sShutting down generator...\n", pad) if err := generatorShutdownFunc(); err != nil { fmt.Printf("failed to shutdown generator: %s\n", err) } }() - // get conduit config template + + // create conduit config from template t, err := template.New("conduit").Parse(conduitConfigTmpl) if err != nil { return fmt.Errorf("unable to parse conduit config template: %w", err) } - // create config file in the right data directory f, err := os.Create(path.Join(dataDir, "conduit.yml")) if err != nil { return fmt.Errorf("problem creating conduit.yml: %w", err) } defer f.Close() - - conduitConfig := config{r.ConduitLogLevel, logfile, - fmt.Sprintf(":%d", r.MetricsPort), - algodNet, r.PostgresConnectionString, + conduitConfig := config{ + LogLevel: r.ConduitLogLevel, + LogFile: conduitlogfile, + MetricsPort: fmt.Sprintf(":%d", r.MetricsPort), + AlgodNet: algodNet, + PostgresConnectionString: r.PostgresConnectionString, } - err = t.Execute(f, conduitConfig) if err != nil { return fmt.Errorf("problem executing template file: %w", err) @@ -196,6 +206,7 @@ func (r *Args) run(reportDirectory string) error { } defer func() { // Shutdown conduit + fmt.Printf("%sShutting down Conduit...\n", pad) if sdErr := conduitShutdownFunc(); sdErr != nil { fmt.Printf("failed to shutdown Conduit: %s\n", sdErr) } @@ -225,6 +236,7 @@ func (r *Args) run(reportDirectory string) error { if err = r.runTest(report, metricsNet, algodNet); err != nil { return err } + fmt.Printf("%sTest completed successfully\n", pad) return nil } @@ -362,15 +374,19 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) // Run for r.RunDuration start := time.Now() + fmt.Printf("%sduration starting now: %s\n", pad, start) count := 1 for time.Since(start) < r.RunDuration { time.Sleep(r.RunDuration / 10) + fmt.Printf("%scollecting metrics (%d)\n", pad, count) if err := collector.Collect(AllMetricNames...); err != nil { return fmt.Errorf("problem collecting metrics (%d / %s): %w", count, time.Since(start), err) } count++ } + + fmt.Printf("%scollecting final metrics\n", pad) if err := collector.Collect(AllMetricNames...); err != nil { return fmt.Errorf("problem collecting final metrics (%d / %s): %w", count, time.Since(start), err) } @@ -438,14 +454,20 @@ func (r *Args) runTest(report *os.File, metricsURL string, generatorURL string) } // startGenerator starts the generator server. -func startGenerator(configFile string, dbround uint64, genesisFile string, verbose bool, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) { +func startGenerator(ledgerLogFile, configFile string, dbround uint64, genesisFile string, verbose bool, addr string, blockMiddleware func(http.Handler) http.Handler) (func() error, generator.Generator) { + f, err := os.OpenFile(ledgerLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + util.MaybeFail(err, "unable to open ledger log file '%s'", ledgerLogFile) + log := logging.NewLogger() + log.SetLevel(logging.Warn) + log.SetOutput(f) + // Start generator. - server, generator := generator.MakeServerWithMiddleware(dbround, genesisFile, configFile, verbose, addr, blockMiddleware) + server, generator := generator.MakeServerWithMiddleware(log, dbround, genesisFile, configFile, verbose, addr, blockMiddleware) // Start the server go func() { // always returns error. ErrServerClosed on graceful close - fmt.Printf("generator serving on %s\n", server.Addr) + fmt.Printf("%sgenerator serving on %s\n", pad, server.Addr) if err := server.ListenAndServe(); err != http.ErrServerClosed { util.MaybeFail(err, "ListenAndServe() failure to start with config file '%s'", configFile) } @@ -464,7 +486,7 @@ func startGenerator(configFile string, dbround uint64, genesisFile string, verbo // startConduit starts the conduit binary. func startConduit(dataDir string, conduitBinary string, round uint64) (func() error, error) { - fmt.Printf("Conduit starting with data directory: %s", dataDir) + fmt.Printf("%sConduit starting with data directory: %s\n", pad, dataDir) cmd := exec.Command( conduitBinary, "-r", strconv.FormatUint(round, 10), @@ -472,9 +494,8 @@ func startConduit(dataDir string, conduitBinary string, round uint64) (func() er ) var stdout bytes.Buffer - var stderr bytes.Buffer cmd.Stdout = &stdout - cmd.Stderr = &stderr + cmd.Stderr = os.Stderr // pass errors to Stderr if err := cmd.Start(); err != nil { return nil, fmt.Errorf("failure calling Start(): %w", err) @@ -489,7 +510,7 @@ func startConduit(dataDir string, conduitBinary string, round uint64) (func() er } } if err := cmd.Wait(); err != nil { - fmt.Printf("Conduit exiting: %s\n", err) + fmt.Printf("%sConduit exiting: %s\n", pad, err) } return nil }, nil diff --git a/tools/block-generator/runner/template/conduit.yml.tmpl b/tools/block-generator/runner/template/conduit.yml.tmpl index c361426ee6..d461dd5647 100644 --- a/tools/block-generator/runner/template/conduit.yml.tmpl +++ b/tools/block-generator/runner/template/conduit.yml.tmpl @@ -5,7 +5,7 @@ log-level: {{.LogLevel}} log-file: {{.LogFile}} # Number of retries to perform after a pipeline plugin error. -retry-count: 10 +retry-count: 120 # Time duration to wait between retry attempts. retry-delay: "1s" diff --git a/tools/x-repo-types/Makefile b/tools/x-repo-types/Makefile index 05094a8484..900b492716 100644 --- a/tools/x-repo-types/Makefile +++ b/tools/x-repo-types/Makefile @@ -1,38 +1,51 @@ -all: goal-v-sdk goal-v-spv +all: clean goal-v-sdk goal-v-spv + +clean: + rm x-repo-types + +x-repo-types: + go build # go-algorand vs go-algorand-sdk: goal-v-sdk: goal-v-sdk-state-delta goal-v-sdk-genesis goal-v-sdk-block goal-v-sdk-blockheader goal-v-sdk-stateproof -goal-v-sdk-state-delta: +goal-v-sdk-state-delta: x-repo-types x-repo-types --x-package "github.com/algorand/go-algorand/ledger/ledgercore" \ --x-type "StateDelta" \ --y-branch "develop" \ --y-package "github.com/algorand/go-algorand-sdk/v2/types" \ --y-type "LedgerStateDelta" -goal-v-sdk-genesis: +goal-v-sdk-genesis: x-repo-types x-repo-types --x-package "github.com/algorand/go-algorand/data/bookkeeping" \ --x-type "Genesis" \ --y-branch "develop" \ --y-package "github.com/algorand/go-algorand-sdk/v2/types" \ --y-type "Genesis" -goal-v-sdk-block: +goal-v-sdk-block: x-repo-types x-repo-types --x-package "github.com/algorand/go-algorand/data/bookkeeping" \ --x-type "Block" \ --y-branch "develop" \ --y-package "github.com/algorand/go-algorand-sdk/v2/types" \ --y-type "Block" -goal-v-sdk-blockheader: +goal-v-sdk-blockheader: x-repo-types x-repo-types --x-package "github.com/algorand/go-algorand/data/bookkeeping" \ --x-type "BlockHeader" \ --y-branch "develop" \ --y-package "github.com/algorand/go-algorand-sdk/v2/types" \ --y-type "BlockHeader" -goal-v-sdk-stateproof: +goal-v-sdk-consensus: x-repo-types + x-repo-types --x-package "github.com/algorand/go-algorand/config" \ + --x-type "ConsensusParams" \ + --y-branch "develop" \ + --y-package "github.com/algorand/go-algorand-sdk/v2/protocol/config" \ + --y-type "ConsensusParams" + +goal-v-sdk-stateproof: x-repo-types x-repo-types --x-package "github.com/algorand/go-algorand/crypto/stateproof" \ --x-type "StateProof" \ --y-branch "develop" \ diff --git a/tools/x-repo-types/go.mod b/tools/x-repo-types/go.mod index 8c0146b70c..df9ddf0114 100644 --- a/tools/x-repo-types/go.mod +++ b/tools/x-repo-types/go.mod @@ -5,7 +5,7 @@ go 1.20 replace github.com/algorand/go-algorand => ../.. require ( - github.com/algorand/go-algorand v0.0.0-20230502140608-e24a35add0bb + github.com/algorand/go-algorand v0.0.0 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 ) diff --git a/util/metrics/metrics.go b/util/metrics/metrics.go index bd68ff4c1e..cb376eb0a3 100644 --- a/util/metrics/metrics.go +++ b/util/metrics/metrics.go @@ -132,4 +132,9 @@ var ( TransactionGroupTxSyncRemember = MetricName{Name: "algod_transaction_group_txsync_remember", Description: "Number of transaction groups remembered via txsync"} // TransactionGroupTxSyncAlreadyCommitted "Number of duplicate or error transaction groups received via txsync" TransactionGroupTxSyncAlreadyCommitted = MetricName{Name: "algod_transaction_group_txsync_err_or_committed", Description: "Number of duplicate or error transaction groups received via txsync"} + + // BroadcastSignedTxGroupSucceeded "Number of successful broadcasts of local signed transaction groups" + BroadcastSignedTxGroupSucceeded = MetricName{Name: "algod_broadcast_txgroup_succeeded", Description: "Number of successful broadcasts of local signed transaction groups"} + // BroadcastSignedTxGroupFailed "Number of failed broadcasts of local signed transaction groups" + BroadcastSignedTxGroupFailed = MetricName{Name: "algod_broadcast_txgroup_failed", Description: "Number of failed broadcasts of local signed transaction groups"} )